This is the multi-page printable view of this section. Click here to print.

Return to the regular view of this page.

Firmware

AVR/ESP32 control firmware — the C++ code that runs on every WII5 buoy.

The Firmware appears as 3 separate versions. AVR C++ code for origianal buoys and the latest, as we moved back to that for extra long battery life. A 32 bit embedded version for ARM, ESP32 versions, and a Linux Embedded version, originally written for Intel Edison. This page is talking about the first release of code based on AVR.

The AVR Control Code is a set of C++ code that is Arduino-compatible and works on multiple platforms including AVR and ESP32. Almost all the work — battery management, GPS, temperature, IMU, accelerometers, power control, Iridium / satellite control — is done here. This is the heart of the current release.

  • C++ source
  • Configuration files
  • Build tools

Source repository

The firmware lives at gitea.sh3d.com.au/Sh3d/WII5Firmware — 72 C++ source files targeting the ATmega2560 at 11.0592 MHz, built with arduino-cli. Entry point: app/wii5_buoy/wii5_buoy.ino; the main control loop lives in WII5Controller.cpp.

Highlights from the firmware

A flavour of what’s in there. Snippets below are trimmed (TODO comments and verbose logging stripped) for readability — each “Source” link jumps to the full original on Gitea.

GPS time and position lock

The GPS loop only updates the system clock once the fix is fresh, valid, and backed by at least five satellites — defensive against drift in marginal conditions.

void WII5GPS::loop() {
  if (running) {
    WII5SerialManager::loop();

    if (
      (waitTime > UPDATE_TIME)
      && (gps->date.isValid())
      && (gps->date.age() < 5000)
      && (gps->time.isValid())
      && (gps->time.age() < 5000)
      && (gps->satellites.isValid())
      && (gps->satellites.value() > 4)
    ) {
      when = now();
      setTime(
        gps->time.hour(), gps->time.minute(), gps->time.second(),
        gps->date.day(), gps->date.month(), gps->date.year()
      );

Source: WII5GPS.cpp lines 123–149

Battery monitoring with low-battery mode switch

A running-average sampler reads BATTERY_1_VOLTS_ANALOG, then a separate analyse step compares the mean against configured thresholds and flips the controller into WII5MODE_LOWBATTERY when it drops.

case WII5BATTERY_MEASURE:
  if (stepCount > 100) {
    step = WII5BATTERY_STORE; stepWait = 0; stepCount = 0;
  }
  else {
    #ifdef BATTERY_1_VOLTS_ANALOG
    uint16_t in = analogRead(BATTERY_1_VOLTS_ANALOG);
    avgBattery1.checkAndAddReading(scale((uint16_t) in, (uint16_t)BATTERY_1_VOLTS_MULT));
    #endif
  }
  break;

case WII5BATTERY_ANALYSE:
  if (
    (value < wii5Config.getBatteryLow())
    && (wii5Controller.getMode() != WII5MODE_LOWBATTERY)
  ) {
    console.log(LOG_INFO, F("Low Battery detected V=%d, Low=%d, Mid=%d"),
      value, wii5Config.getBatteryLow(), wii5Config.getBatteryMid()
    );
    wii5Controller.setMode(WII5MODE_LOWBATTERY);
  }

Source: WII5Battery.cpp lines 86–127

Iridium signal-quality polling with retry backoff

Before transmitting an SBD message, the buoy polls signal quality (AT+CSQ) and retries up to IRIDIUM_RETRY_MAX times if it’s below the configured minimum — saves power and avoids burning credits on doomed transmissions.

case WII5IRIDIUM_SIGNALQUALITY:
  if (first) { sendCount = 0; }
  else if (stepWait > 500) {
    if (sendCount == 0) {
      programLine(7);
      setTimeout(30000);
    }
    switch (sendAndWait(WII5SERIALLAST_ATCSQ)) {
      case WII5SERIALCMD_WAITING:
        break;
      case WII5SERIALCMD_OK:
      default:
        if (lastVal < sigQualMin) {
          if (sigQualRetry < IRIDIUM_RETRY_MAX) {
            sigQualRetry++;
            step = WII5IRIDIUM_SIGNALQUALITY_WAIT; stepWait = 0;
          }
          else {
            lastSignalQuality = int(lastVal);
            step = WII5IRIDIUM_SEND_JUMP; stepWait = 0;
          }
        }

Source: WII5Iridium.cpp lines 329–365

Binary payload splitting across 340-byte SBD limits

Iridium short-burst-data messages are capped at 340 bytes. getSplit walks the per-field bitmask in_t, packing as many fields as will fit into one message and returning the rest as a continuation.

bool WII5BinData::getSplit(uint32_t in_t, uint16_t max_size,
                           uint8_t* start_bit, uint32_t* out_t) {
  uint16_t ret_size = sizeof(WII5_BINDATA_HEADER);
  uint16_t part_size = 0;
  *out_t = 0;

  for (uint8_t b = *start_bit; b < 16; b++) {
    if (BitVal(in_t, b)) {
      part_size = getSizeOne(b);
      if ((ret_size + part_size) > max_size) {
        *start_bit = b;
        if (*out_t == 0) {
          console.log(LOG_FATAL, F("BinData: Failed to split BinData to this size"));
          return false;
        }
        return true;
      }
      SetBit(*out_t, b);
      ret_size += part_size;
    }
  }

Source: WII5BinData.cpp lines 89–116

Deep-sleep cycle with peripheral shutdown

WII5SLEEP_WAIT powers everything down — GPS, Iridium, IMU, SD, the shared 5 V rail — waits 20 s for things to settle, then WII5SLEEP_SLEEPING calls into the AVR sleep routine for the configured period.

case WII5SLEEP_WAIT:
  if (first) {
    #ifdef WII5_GPS
    wii5Gps.off();
    #endif
    wii5Communications.stop();
    wii5Iridium.stop();
    wii5Sparton.stop();
    wii5Controller.setSDOff();
    wii5Controller.shared5Off();
  }
  else if (stepWait > 20000) {
    step = WII5SLEEP_SLEEPING; stepWait = 0;
  }
  break;

case WII5SLEEP_SLEEPING:
  if (sleepNextSeconds > 21600) {
    sleepNextSeconds = 3600;   // safety cap: max 6 h
  }
  console.log(LOG_INFO, F("Sleep: sleeping %lu seconds"), sleepNextSeconds);
  wii5Setup.sleepBefore();
  sh3dNodeUtil.sleep(sleepNextSeconds);
  wii5Setup.sleepAfter();

Source: WII5ModeSleep.cpp lines 92–136

@WII5,cmd,sub,arg command dispatcher

The buoy speaks an AT-style CSV protocol over serial and over Iridium. A @-prefixed line is parsed into command / sub-command / argument and dispatched through runCommand — same code path for local console and remote satellite command.

bool WII5Commands::processConsoleAT() {
  if (!console.available() || (console.getCommand() != '@'))
    return false;

  if (strcmp_P(console.getCsvBuffer(0), (PGM_P) F("WII5")) == 0) {
    return runCommand(
      wii5Strings.parseCommand(
        console.getCsvBuffer(1),
        console.getCsvBuffer(2),
        console.getCsvBuffer(3)
      )
    );
  }
  else if (strcmp_P(console.getCsvBuffer(0), (PGM_P) F("Help")) == 0) {
    return wii5Help.processConsoleCsv();
  }
  return false;
}

Source: WII5Commands.cpp lines 1407–1425