Weltweiter kostenloser Versand ab 120 € Bestellwert – Zahlung mit PayPal und Stripe – Hergestellt in Finnland

Ruuvi Firmware – Teil 13: Batteriemessung mit Funk synchronisiert

Introbild zur Ruuvi-Firmware-Serie, Teil 13

In diesem Teil des Tutorials synchronisieren wir die Messung der Batteriespannung mit Funkübertragungen. Den finalen Code dieses Blogposts kannst du bei Ruuvi GitHub im Branch ruuviblog, Tag 3.13.0-alpha herunterladen.

Bitte folge Teil 1 der Serie für Details zum Klonen des Repositories und Kompilieren des Codes. Die finale Hex-Datei dieses Tutorials kann vom Ruuvi Jenkins heruntergeladen werden.

Update auf SDK 15.2

Im vorherigen Teil des Blogs haben wir das Nordic Software Development Kit (SDK) in Version 15.2 für den Bootloader-Code und Softdevice s132 6.1.0 als Funk-Software verwendet. Aktualisieren wir jetzt auch das Hauptprojekt.

Das Update ist schnell erledigt: Wir suchen das alte SDK und ersetzen es in der Projektdatei durch das neue SDK und entfernen das Präfix „experimental_“ in den Pfaden log, ringbuf und memobj. Die Datei pm_mutex.c wird entfernt. Zusätzlich #define NRF_LOG_STR_PUSH_BUFFER_SIZE APPLICATION_LOG_BUFFER_SIZE in nrf5_sdk15_application_config.h, damit die Makrobenennung zum SDK15.2 -Logging passt. Das Logging nutzt jetzt ringbuf.c, wir fügen es dem Build hinzu. Dann reservieren wir in unserer flash_placement.xml Platz für log_backends, indem wir eine Definition zu den Flash-Sektionen hinzufügen:

<ProgramSection alignment="4" keep="Yes" load="Yes" name=".log_backends" inputsections="*(SORT(.log_backends*))" address_symbol="__start_log_backends" end_symbol="__stop_log_backends" />

Die Clock-Enable-Direktive hat sich ebenfalls von CLOCK_ENABLED zu NRF_CLOCK_ENABLED geändert, daher fügen wir einen Wrapper in sdk_config.h hinzu

#ifndef NRF_CLOCK_ENABLED
#define NRF_CLOCK_ENABLED CLOCK_ENABLED
#endif

Nordic hat dem SDK Definitionen für nfc_fixes.h hinzugefügt, wir benötigen daher die Definition HAL_NFC_ENGINEERING_BC_FTPAN_WORKAROUND in den in ruuvi.firmware.c.emProject definierten Preprocessor-Makros nicht mehr. Nach dem Entfernen kompiliert das Projekt ohne Warnungen, und wir können auf Basis des aktualisierten SDK weiterbauen. Vor dem Flashen muss die Softdevice-Version in der Projektdatei auf 6.1.0 aktualisiert werden.

Batteriemessung erneut betrachtet

Seit Teil 11 — Scheduler messen wir die Batteriespannung in einem Intervall von ungefähr 30 Sekunden, ohne darauf zu achten, wie das Programm sonst läuft.

Das ist bei einer frischen Batterie bei Raumtemperatur ein guter Ansatz. Wenn jedoch der Innenwiderstand der Batterie durch Kälte und Entladung steigt, erleben wir während der Spitzenlast – der Funkaktivität – einen Spannungseinbruch an der Batterie.

Daher registrieren wir einen Event-Handler, der nach einer Funkübertragung aufgerufen wird, und prüfen darin, ob es Zeit ist, eine weitere Messung mit dem Analog-Digital-Wandler (ADC) an der Batterie durchzuführen.

Schnittstelle

Wir haben bereits ein Funkaktivitäts-Event in ruuvi_interface_communication_radio.h definiert. Wir fügen eine Funktion hinzu, um in Zeile 41 einen Application-Event-Handler für Funk-Interrupts einzurichten:

typedef enum
{
  RUUVI_INTERFACE_COMMUNICATION_RADIO_BEFORE,
  RUUVI_INTERFACE_COMMUNICATION_RADIO_AFTER
}ruuvi_interface_communication_radio_activity_evt_t;

typedef enum
{
  RUUVI_INTERFACE_COMMUNICATION_RADIO_UNINIT = 0,
  RUUVI_INTERFACE_COMMUNICATION_RADIO_ADVERTISEMENT,
  RUUVI_INTERFACE_COMMUNICATION_RADIO_GATT,
  RUUVI_INTERFACE_COMMUNICATION_RADIO_MESH
}ruuvi_interface_communication_radio_user_t;

/**
 *  Type of radio activity interrupt. This is common to all radio modules, i,e, the callback gets called for every radio action.
 */
typedef void(*ruuvi_interface_communication_radio_activity_interrupt_fp_t)(const ruuvi_interface_communication_radio_activity_evt_t evt);

// Enable / disable radio stacks
ruuvi_driver_status_t ruuvi_interface_communication_radio_init  (const ruuvi_interface_communication_radio_user_t handle);
ruuvi_driver_status_t ruuvi_interface_communication_radio_uninit(const ruuvi_interface_communication_radio_user_t handle);

/**
 * Writes maximum 64-bit unique address of the device to the pointer. This address
 * may be changed during runtime. The address is identifier of the device on radio network,
 * such as BLE MAC address.
 *
 * parameter address: Output, value of address.
 *
 * return RUUVI_DRIVER_SUCCESS on success
 * return RUUVI_DRIVER_ERROR_NOT_SUPPORTED if address cannot be returned on given platform
 */
ruuvi_driver_status_t ruuvi_interface_communication_radio_address_get(uint64_t* const address);

/**
 * Setup radio activity interrupt
 *
 * parameter handler: Function to call on radio event NULL to disable radio-level callback, however module-level callbacks (advertising, GATT etc) will be called.
 */
void ruuvi_interface_communication_radio_activity_callback_set(const ruuvi_interface_communication_radio_activity_interrupt_fp_t handler);

Rohdaten anzeigen

Treiber

Als Nächstes implementieren wir die Schnittstelle in nrf5_sdk15_platform/ruuvi_platform_communication_radio.c. Wir fügen einen statischen Funktionszeiger hinzu, der gesetzt werden soll, sowie eine Setup-Funktion. Dann fügen wir einen Handler für den Softdevice-Funkinterrupt hinzu, der den Funktionszeiger aufruft, sowie einen Event-Handler des Advertising-Moduls, falls wir später advertising-spezifische Aktionen ausführen möchten.

In Zeile 43 haben wir die Interrupt-Initialisierung ble_radio_notification hinzugefügt. Die Interrupt-Priorität ist in application_config/ruuvi_platform_nrf5_sdk15_config.h als 2 definiert, das ist die höchste Application-Priorität. „Distance“ definiert, wie lange vor und nach dem Event der Interrupt ausgelöst wird; 800 μs ist der kleinste Abstand, den wir erreichen können.

// Application callback for radio events
static ruuvi_interface_communication_radio_activity_interrupt_fp_t on_radio_activity_callback = NULL;

/**
 * Task to run on radio activity - call event handlers of radio modules and a common radio event handler.
 * This function is in interrupt context, avoid long processing or using peripherals.
 * Schedule any long tasks in application callbacks.
 *
 * parameter active: True if radio is going to be active after event, false if radio was turned off (after tx/rx)
 */
static void on_radio_evt(bool active)
{

  ruuvi_interface_communication_radio_activity_evt_t evt = active ? RUUVI_INTERFACE_COMMUNICATION_RADIO_BEFORE : RUUVI_INTERFACE_COMMUNICATION_RADIO_AFTER;
  // Call advertising event handler
  ruuvi_platform_communication_ble4_advertising_activity_handler(evt);

  // Call common event handler if set
  if(NULL != on_radio_activity_callback ){ on_radio_activity_callback(evt); }

}

ruuvi_driver_status_t ruuvi_interface_communication_radio_init  (const ruuvi_interface_communication_radio_user_t _handle)
{
  if(RUUVI_INTERFACE_COMMUNICATION_RADIO_UNINIT != handle) { return RUUVI_DRIVER_ERROR_INVALID_STATE; }

  ret_code_t err_code = NRF_SUCCESS;
  handle = _handle;
  err_code = nrf_sdh_enable_request();
  RUUVI_DRIVER_ERROR_CHECK(err_code, NRF_SUCCESS);

  // Configure the BLE stack using the default settings.
  // Fetch the start address of the application RAM.
  uint32_t ram_start = 0;
  err_code |= nrf_sdh_ble_default_cfg_set(NRF5_SDK15_BLE4_STACK_CONN_TAG, &ram_start);
  RUUVI_DRIVER_ERROR_CHECK(err_code, NRF_SUCCESS);

  // Enable BLE stack.
  err_code |= nrf_sdh_ble_enable(&ram_start);
  RUUVI_DRIVER_ERROR_CHECK(err_code, NRF_SUCCESS);

  // Initialize radio interrupts
  err_code |= ble_radio_notification_init(NRF5_SDK15_RADIO_IRQ_PRIORITY,
                                          NRF_RADIO_NOTIFICATION_DISTANCE_800US,
                                          on_radio_evt);

  return ruuvi_platform_to_ruuvi_error(&err_code);
}

.
.
.

void ruuvi_interface_communication_radio_activity_callback_set(const ruuvi_interface_communication_radio_activity_interrupt_fp_t handler)
{
  // Warn user if CB is not NULL and non-null pointer is set, do not overwrite previous pointer.
  if(NULL != handler && NULL != on_radio_activity_callback) { RUUVI_DRIVER_ERROR_CHECK(RUUVI_DRIVER_ERROR_INVALID_STATE, ~RUUVI_DRIVER_ERROR_FATAL); }
  else { on_radio_activity_callback = handler; }
}

Rohdaten anzeigen

Testen

Der Übersichtlichkeit halber ist der Zwischenstand während der Entwicklung hier nicht enthalten, der finale Code wird später gezeigt. Kurz gesagt: Die ADC-Task wurde so eingestellt, dass sie beim Funkinterrupt den ADC sampelt und einen General-Purpose-Input/Output-Pin (GPIO) toggelt. Dieses Toggling wurde genutzt, um Oszilloskopmessungen an der Versorgungsspannung zu triggern. Zuerst wurde eine wenig genutzte Batterie getestet.

Oszilloskop zeigt DC- und AC-Kopplung an der Batteriespannung
Blaue Linie ist DC-gekoppelt an GPIO, rote Linie ist AC-gekoppelt an der Batteriespannung

Die AC-gekoppelte Linie zeigt, dass wir einen Einbruch von etwa 150 mV auf der Leitung haben, und das ADC-Sample trifft nicht exakt den niedrigsten Punkt. Die Versorgungsspannung liegt etwas über 3 V. Als Nächstes prüfen wir eine entladene CR2450-Batterie aus der Kickstarter-Ära 2016.

Oszilloskop gekoppelt an GPIO und Batteriespannung
Blaue Linie ist DC-gekoppelt an GPIO, rote Linie ist AC-gekoppelt an der Batteriespannung

Hier sehen wir einen deutlich tieferen Einbruch von über 500 mV, obwohl die Ruhespannung ebenfalls etwas über 3 V liegt. Das bringt uns auf eine Idee: Wie wäre es, statt der absoluten Spannung die Tiefe des Spannungseinbruchs zu nutzen, um den Batteriezustand zu bestimmen? Diese Idee schauen wir uns später noch an.

Integration in die Anwendung

Jetzt haben wir 3 unterschiedliche Modi für den ADC-Betrieb:

  • Einfach: In regelmäßigen Intervallen messen
  • Funk: Nach Funkaktivität messen
  • Droop: Spannung nach Funkaktivität und einige Millisekunden später messen, um die Tiefe des Spannungseinbruchs zu sehen

Definieren wir diese Modi in application_config.h

/**
 * Data format configuration
 */
// Voltage mode - select one
#ifndef APPLICATION_BATTERY_VOLTAGE_MODE
#define APPLICATION_BATTERY_VOLTAGE_SIMPLE   0  // Simple mode: Not synchronized to anything, sampled at regular interval
#define APPLICATION_BATTERY_VOLTAGE_RADIO    1  // Radio mode: Voltage is sampled after radio tx, refreshed after interval has passed.
#define APPLICATION_BATTERY_VOLTAGE_DROOP    0  // Droop mode: Battery is read after TX and after a brief recovery period. Droop is reported
#endif
#define APPLICATION_BATTERY_DROOP_DELAY_MS   2  // Milliseconds between active and recovered tx
#if(APPLICATION_BATTERY_VOLTAGE_SIMPLE + APPLICATION_BATTERY_VOLTAGE_RADIO + APPLICATION_BATTERY_VOLTAGE_DROOP != 1)
  #error "Select application battery voltage monitor mode by defining one constant as 1"
#endif

Rohdaten anzeigen

Als Nächstes aktualisieren wir task_adc.c: Initialisierung, Timer– und Scheduler-Funktionen, und registrieren die ADC-Task beim Funk.

RUUVI_PLATFORM_TIMER_ID_DEF(adc_timer);
static ruuvi_driver_sensor_t adc_sensor = {0};
static uint64_t t_sample = 0;
static float droop = 0;
static float after_tx = 0;

/* Use these functions for using ADC at regular, timed intervals
 * Remember to start the timer at init
 */
//handler for scheduled accelerometer event
static void task_adc_scheduler_task(void *p_event_data, uint16_t event_size)
{
  ruuvi_driver_status_t status = RUUVI_DRIVER_SUCCESS;

  // Take new sample
  status |= task_adc_sample();
  // Log warning if adc sampling failed.
  RUUVI_DRIVER_ERROR_CHECK(status, ~RUUVI_DRIVER_ERROR_FATAL);
}

// Timer callback, schedule event here or execute it right away if it's timing-critical
static void task_adc_timer_cb(void* p_context)
{
  ruuvi_driver_status_t status = RUUVI_DRIVER_SUCCESS;
  ruuvi_interface_adc_data_t rest;

  // Simple mode
  if(APPLICATION_BATTERY_VOLTAGE_SIMPLE)
  {
    ruuvi_platform_scheduler_event_put(NULL, 0, task_adc_scheduler_task);
  }

  // Droop mode
  if(APPLICATION_BATTERY_VOLTAGE_DROOP)
  {
  // Take new ADC sample
  status |= task_adc_sample();

  // Read new sample
  status |= adc_sensor.data_get(&rest);
  RUUVI_DRIVER_ERROR_CHECK(status, RUUVI_DRIVER_SUCCESS);

  // Subtract active sample from rest sample
  droop = rest.adc_v - after_tx;
  if(0.0 > droop) { droop = 0.0; }
  }
}


// Callback for radio event. Configured to radio on TASK ADC INIT on droop mode and radio mode,
// see application_config.h
static void task_adc_trigger_on_radio(const ruuvi_interface_communication_radio_activity_evt_t evt)
{

  // If event is after radio activity
  if(RUUVI_INTERFACE_COMMUNICATION_RADIO_AFTER == evt)
  {
    // Sample
    if((APPLICATION_ADC_SAMPLE_INTERVAL_MS < ruuvi_platform_rtc_millis() - t_sample || 0 == t_sample))
    {
      // TEST: LED ON to see the sample time relative to supply voltage, oscilloscope triggered by this signal
      // task_led_write(RUUVI_BOARD_LED_GREEN, RUUVI_BOARD_LEDS_ACTIVE_STATE);

      t_sample = ruuvi_platform_rtc_millis();
      task_adc_sample();

      // Read radio-synched ADC sample
      ruuvi_driver_status_t status = RUUVI_DRIVER_SUCCESS;
      ruuvi_interface_adc_data_t active;
      status |= adc_sensor.data_get(&active);
      RUUVI_DRIVER_ERROR_CHECK(status, RUUVI_DRIVER_SUCCESS);
      after_tx = active.adc_v;

      // task_led_write(RUUVI_BOARD_LED_GREEN, !RUUVI_BOARD_LEDS_ACTIVE_STATE);

      // Sample again for droop mode
      if(APPLICATION_BATTERY_VOLTAGE_DROOP)
      {
        ruuvi_platform_timer_start(adc_timer, APPLICATION_BATTERY_DROOP_DELAY_MS);
      }
    }
  }
}

static ruuvi_driver_status_t task_adc_configure(void)
{
  ruuvi_driver_sensor_configuration_t config;
  config.samplerate    = APPLICATION_ADC_SAMPLERATE;
  config.resolution    = APPLICATION_ADC_RESOLUTION;
  config.scale         = APPLICATION_ADC_SCALE;
  config.dsp_function  = APPLICATION_ADC_DSPFUNC;
  config.dsp_parameter = APPLICATION_ADC_DSPPARAM;
  config.mode          = APPLICATION_ADC_MODE;
  if(NULL == adc_sensor.data_get) { return RUUVI_DRIVER_ERROR_INVALID_STATE; }
  return adc_sensor.configuration_set(&adc_sensor, &config);
}

ruuvi_driver_status_t task_adc_init(void)
{
  ruuvi_driver_status_t err_code = RUUVI_DRIVER_SUCCESS;
  ruuvi_driver_bus_t bus = RUUVI_DRIVER_BUS_NONE;
  uint8_t handle = RUUVI_INTERFACE_ADC_AINVDD;

  // Initialize timer for adc task.
  // Note: the timer is not started.
  // Note: use REPEATED mode for free-running sampling.
  ruuvi_interface_timer_mode_t mode = APPLICATION_BATTERY_VOLTAGE_SIMPLE ? RUUVI_INTERFACE_TIMER_MODE_REPEATED : RUUVI_INTERFACE_TIMER_MODE_SINGLE_SHOT;
  err_code |= ruuvi_platform_timer_create(&adc_timer, mode, task_adc_timer_cb);

  err_code |= ruuvi_interface_adc_mcu_init(&adc_sensor, bus, handle);
  RUUVI_DRIVER_ERROR_CHECK(err_code, RUUVI_DRIVER_SUCCESS);

  if(RUUVI_DRIVER_SUCCESS == err_code)
  {
    err_code |= task_adc_configure();

    // Start the ADC timer here if you use timer-based battery measurement
    if(true == APPLICATION_BATTERY_VOLTAGE_SIMPLE)
    {
      err_code |= ruuvi_platform_timer_start(adc_timer, APPLICATION_ADC_SAMPLE_INTERVAL_MS);
    }

    // Configure the radio callback here to synchronize radio to ADC, unless we're in simple mode
    if(false == APPLICATION_BATTERY_VOLTAGE_SIMPLE)
    {
      ruuvi_interface_communication_radio_activity_callback_set(task_adc_trigger_on_radio);
    }
    return err_code;
  }

  // Return error if ADC could not be configured
  return RUUVI_DRIVER_ERROR_NOT_FOUND;
}

.
.
.

ruuvi_driver_status_t task_adc_battery_get(ruuvi_interface_adc_data_t* const data)
{
  if(NULL == data) { return RUUVI_DRIVER_ERROR_NULL; }
  if(NULL == adc_sensor.data_get) { return RUUVI_DRIVER_ERROR_INVALID_STATE; }
  ruuvi_driver_status_t err_code = RUUVI_DRIVER_SUCCESS;
  data->timestamp_ms = t_sample;
  if(APPLICATION_BATTERY_VOLTAGE_SIMPLE) { err_code |= task_adc_data_get(data); }
  if(APPLICATION_BATTERY_VOLTAGE_RADIO)  { data->adc_v = after_tx; }
  if(APPLICATION_BATTERY_VOLTAGE_DROOP)  { data->adc_v = droop; }
  return err_code;
}

Rohdaten anzeigen

Unsere Scheduler-Task bleibt unverändert: Wir sampeln den ADC, wenn der Scheduler dazu kommt. Unsere Timer-Task enthält jetzt deutlich mehr: Im Einfach-Modus planen wir das ADC-Lesen wie zuvor, im Droop-Modus sampeln wir die Batterie jedoch sofort erneut und speichern die Differenz zur funksynchronisierten Messung. Ist die Differenz kleiner als null, setzen wir sie auf null, da das Datenformat einen unsigned Wert erwartet.

Der Funk-Callback nimmt ein Sample, wenn das Event nach Funkaktivität liegt und seit dem letzten Sample-Intervall genug Zeit vergangen ist, und liest es in die Variable after_tx.

Zum Schluss definieren wir task_adc_battery_get, das entscheidet, ob die Anwendung das neueste Sample, das nach der Übertragung gespeicherte Sample oder den Droop-Wert erhalten soll.

Dann passen wir task_advertising.c an, um die Batteriespannung aus der neuen Task zu beziehen.

Leistungsprofiling

Dieses Leistungsprofil wurde im Modus funksynchronisiert aufgenommen, also ohne zusätzliches Aufwachen 2 ms nach der Messung. Unser letztes Ergebnis lag knapp unter 23 μA, und es scheint, dass diese Synchronisierung keinen Einfluss auf den Stromverbrauch hatte.

BME280-Sample
Wir sehen die Programmereignisse: BME280-Sample, Sensoren auslesen, Daten im Profil übertragen.

Bootloader-Integration

Bis hierhin haben wir nur eine kabelgebundene Verbindung genutzt. Im letzten Teil haben wir einen Bootloader für unser Projekt erstellt – nutzen wir ihn. Da das Segger Embedded Studio (SES)-Projekt nrfutil und die Generierung der Bootloader-Settings nicht „versteht“, müssen wir einen manuellen Schritt hinzufügen, um das mit SES erstellte Projekt auf den RuuviTag zu flashen.

Wir erstellen ein Skript „flash.sh“, das den veröffentlichten Bootloader und den Encryption-Key von GitHub herunterlädt und ein Paket erstellt, das Softdevice, Bootloader, Bootloader-Konfiguration und Application-Hex enthält, sowie ein DFU-Paket für die Anwendung.

#!/bin/bash
NAME="dev"
VERSION="0.0.1"
while getopts "n:v:" option;
do
case "${option}"
in
n) NAME=${OPTARG};;
v) VERSION=${OPTARG};;
esac
done

bootloader="ruuvitag_b_bootloader_3.0.0_s132_6.1.0_debug.hex"     
if [ -f $bootloader ]; then
   echo "Found bootloader."
else
   wget https://github.com/ruuvi/ruuvi.nrf5_sdk15_bootloader.c/releases/download/3.0.0/ruuvitag_b_bootloader_3.0.0_s132_6.1.0_debug.hex
fi

key="ruuvi_open_private.pem"     
if [ -f $key ]; then
   echo "Found key"
else
   wget https://github.com/ruuvi/ruuvi.nrf5_sdk15_bootloader.c/releases/download/3.0.0/ruuvi_open_private.pem
fi

nrfutil settings generate --family NRF52 --application Output/Debug/Exe/ruuvi.firmware.c.hex --application-version 1 --application-version-string "$VERSION" --bootloader-version 1 --bl-settings-version 1 settings.hex
mergehex -m ../../../../nRF5_SDK_15.2.0_9412b96/components/softdevice/s132/hex/s132_nrf52_6.1.0_softdevice.hex ruuvitag_b_bootloader_3.0.0_s132_6.1.0_debug.hex settings.hex -o sbc.hex
mergehex -m sbc.hex Output/Debug/Exe/ruuvi.firmware.c.hex -o packet.hex
nrfjprog --family nrf52 --eraseall
nrfjprog --family nrf52 --program packet.hex
nrfjprog --family nrf52 --reset

mv packet.hex ruuvitag_b\_ses\_$NAME\_$VERSION\_full.hex
cp Output/Debug/Exe/ruuvi.firmware.c.hex ruuvitag_b\_ses\_$NAME\_$VERSION\_app.hex
nrfutil pkg generate --application Output/Debug/Exe/ruuvi.firmware.c.hex --application-version 1 --application-version-string "$VERSION" --hw-version 0x0b --sd-req 0xA9 --key-file ruuvi_open_private.pem ruuvitag_b\_ses\_$NAME\_$VERSION\_dfu.zip

Rohdaten anzeigen

Das Skript prüft auf die Argumente -n und -v für Name und Version und verwendet standardmäßig dev 0.0.1, wenn diese nicht angegeben sind. Danach prüft das Skript, ob Bootloader und der private Schlüssel zum Signieren des Pakets vorhanden sind, und lädt sie andernfalls von GitHub herunter.

Dann erstellt das Skript die Bootloader-Settings und erzeugt das Zwischenpaket sbc.hex, das Softdevice+Bootloader mit konfigurierten Application-Settings enthält. Dieses Paket wird anschließend mit der Anwendung zusammengeführt, um ein fertiges Paket zu erstellen, mit dem das komplette Programm auf den RuuviTag geflasht werden kann.

Der RuuviTag wird gelöscht und das vollständige Paket wird geflasht.

Zum Schluss bekommen die Pakete sprechende Namen, zum Beispiel ergibt das Ausführen

./flash.sh -n ruuvifw -v 3.13.0

folgendes:

  • ruuvitag_b_ses_ruuvifw_3.13.0_full.hex
  • ruuvitag_b_ses_ruuvifw_3.13.0_app.hex
  • ruuvitag_b_ses_ruuvifw_3.13.0_dfu.zip

ARMGCC

Als Nächstes erstellen wir ein Packaging-Skript für unsere ARMGCC-Builds, damit Jenkins ähnliche Pakete erstellen kann. Wenn wir schon dabei sind, bauen wir Unterstützung für Batch-Builds in verschiedenen Modi ein, indem per Command-Line-Flag ausgewählt wird, welche Settings in application_config.h verwendet werden.

Wir fügen application_modes.h hinzu, das Header mit Modus-Definitionen einbindet, wenn ein bestimmter Modus definiert ist, und wir fügen application_mode_longlife.h als Beispiel hinzu.

#ifndef APPLICATION_MODES_H
#define APPLICATION_MODES_H
#ifdef APPLICATION_MODE_LONGLIFE
  #include "application_mode_longlife.h"
#endif

#endif

Rohdaten anzeigen

/**
 * Environmental sensor configuration
 **/
// Sample rate is in Hz. This configures only the sensor, not transmission rate of data.
#define APPLICATION_ENVIRONMENTAL_CONFIGURED
#define APPLICATION_ENVIRONMENTAL_SAMPLERATE RUUVI_DRIVER_SENSOR_CFG_MIN

// Resolution and scale cannot be adjusted on BME280
#define APPLICATION_ENVIRONMENTAL_RESOLUTION RUUVI_DRIVER_SENSOR_CFG_DEFAULT
#define APPLICATION_ENVIRONMENTAL_SCALE      RUUVI_DRIVER_SENSOR_CFG_DEFAULT

// Valid values for BME280 are: (RUUVI_DRIVER_SENSOR_DSP_)LAST, IIR, OS
// IIR slows step response but lowers noise
// OS increases power consumption but lowers noise.
// See https://blog.ruuvi.com/humidity-sensor-673c5b7636fc and https://blog.ruuvi.com/dsp-compromises-3f264a6b6344
#define APPLICATION_ENVIRONMENTAL_DSPFUNC    RUUVI_DRIVER_SENSOR_DSP_IIR

// No effect on _LAST, use 1. On _OS and _IIR valid values are 2, 4, 8 and 16.
#define APPLICATION_ENVIRONMENTAL_DSPPARAM   RUUVI_DRIVER_SENSOR_CFG_MAX

// (RUUVI_DRIVER_SENSOR_CFG_)SLEEP, SINGLE or CONTINUOUS
#define APPLICATION_ENVIRONMENTAL_MODE       RUUVI_DRIVER_SENSOR_CFG_CONTINUOUS


/**
 * Accelerometer configuration
 **/
#define APPLICATION_ACCELERATION_CONFIGURED
// 1, 10, 25, 50, 100, 200 for LIS2DH12
#define APPLICATION_ACCELEROMETER_SAMPLERATE RUUVI_DRIVER_SENSOR_CFG_MIN

// 8, 10, 12 for LIS2DH12
#define APPLICATION_ACCELEROMETER_RESOLUTION 10

// 2, 4, 8, 16 for LIS2DH12
#define APPLICATION_ACCELEROMETER_SCALE   RUUVI_DRIVER_SENSOR_CFG_MIN

// LAST or HIGH_PASS
#define APPLICATION_ACCELEROMETER_DSPFUNC RUUVI_DRIVER_SENSOR_DSP_LAST
#define APPLICATION_ACCELEROMETER_DSPPARAM 1

// SLEEP or CONTINUOUS
#define APPLICATION_ACCELEROMETER_MODE RUUVI_DRIVER_SENSOR_CFG_CONTINUOUS

/**
 * Bluetooth configuration
 *
 */
// Avoid "even" values such as 100 or 1000 to eventually drift apart from the devices transmitting at same interval
// Min 100, Max 10000
#define APPLICATION_ADVERTISING_CONFIGURED
#define APPLICATION_ADVERTISING_INTERVAL 9990
#define APPLICATION_ADVERTISING_POWER    RUUVI_BOARD_TX_POWER_MAX

Rohdaten anzeigen

Unsere Hauptkonfigurationsdatei application_config.h enthält die Definitionen in #ifdef-Klauseln und #definet Standardwerte, sofern nicht etwas anderes #definiert ist.

Dann fügen wir das Compile-Flag in unser Root-Makefile ein mit

MODE=-DAPPLICATION_MODE_LONGLIFE
.PHONY: all fw clean

all: fw 

fw:
	@echo build FW
	git submodule update --init --recursive
	$(MAKE) -C targets/ruuvitag_b/armgcc clean
	$(MAKE) -C targets/ruuvitag_b/armgcc MODE=-DAPPLICATION_MODE_LONGLIFE
	targets/ruuvitag_b/armgcc/package.sh -n ruuvifw_longlife
	$(MAKE) -C targets/ruuvitag_b/armgcc clean
	$(MAKE) -C targets/ruuvitag_b/armgcc
	targets/ruuvitag_b/armgcc/package.sh -n ruuvifw_default


clean:
	@echo cleaning build files…
	$(MAKE) -C targets/ruuvitag_b/armgcc clean

Rohdaten anzeigen

Unser ARMGCC-Makefile hängt das Modus-Argument an CFLAGS an.

Nachdem das Programm gebaut wurde, erstellt unser Package-Skript die Pakete für App-, Full- und DFU-Versionen und benennt die Version mit dem GitHub-Commit-Hash.

#!/bin/bash
cd "$(dirname "$0")"
NAME="ruuvifw"
VERSION=$(git rev-parse --short HEAD)
while getopts "n:v:" option;
do
case "${option}"
in
n) NAME=${OPTARG};;
v) VERSION=${OPTARG};;
esac
done

bootloader="ruuvitag_b_bootloader_3.0.0_s132_6.1.0_debug.hex"     
if [ -f $bootloader ]; then
   echo "Found bootloader."
else
   wget https://github.com/ruuvi/ruuvi.nrf5_sdk15_bootloader.c/releases/download/3.0.0/ruuvitag_b_bootloader_3.0.0_s132_6.1.0_debug.hex
fi

key="ruuvi_open_private.pem"     
if [ -f $key ]; then
   echo "Found key"
else
   wget https://github.com/ruuvi/ruuvi.nrf5_sdk15_bootloader.c/releases/download/3.0.0/ruuvi_open_private.pem
fi

nrfutil settings generate --family NRF52 --application _build/nrf52832_xxaa.hex --application-version 1  --bootloader-version 1 --bl-settings-version 1 settings.hex
mergehex -m ../../../../nRF5_SDK_15.2.0_9412b96/components/softdevice/s132/hex/s132_nrf52_6.1.0_softdevice.hex ruuvitag_b_bootloader_3.0.0_s132_6.1.0_debug.hex settings.hex -o sbc.hex
mergehex -m sbc.hex _build/nrf52832_xxaa.hex -o packet.hex

rm ruuvitag_b_armgcc*$NAME*.hex
rm ruuvitag_b_armgcc*$NAME*.zip

mv packet.hex ruuvitag_b\_armgcc\_$NAME\_$VERSION\_full.hex
cp _build/nrf52832_xxaa.hex ruuvitag_b\_armgcc\_$NAME\_$VERSION\_app.hex
nrfutil pkg generate --application _build/nrf52832_xxaa.hex --application-version 1 --hw-version 0x0b --sd-req 0xA9 --key-file ruuvi_open_private.pem ruuvitag_b\_armgcc\_$NAME\_$VERSION\_dfu.zip

Rohdaten anzeigen

Das erzeugt

  • ruuvitag_b_armgcc_ruuvifw_default_cf94d5d_app.hex
  • ruuvitag_b_armgcc_ruuvifw_default_cf94d5d_dfu.zip
  • ruuvitag_b_armgcc_ruuvifw_default_cf94d5d_full.hex
  • ruuvitag_b_armgcc_ruuvifw_longlife_cf94d5d_app.hex
  • ruuvitag_b_armgcc_ruuvifw_longlife_cf94d5d_dfu.zip
  • ruuvitag_b_armgcc_ruuvifw_longlife_cf94d5d_full.hex

und entfernt alte Binärdateien. Dann konfigurieren wir Jenkins so, dass die Build-Artefakte gespeichert werden. Danke Scrin für den Vorschlag!

Screenshot, der zeigt, dass unsere Long-Life-Variante den Stromverbrauch halbiert hat.
Unsere Long-Life-Variante hat den Stromverbrauch halbiert.

Fazit

Wir haben jetzt eine präzisere Batteriemessung und ein paar Ideen, wie wir die Schätzung der verbleibenden Laufzeit verbessern können. Außerdem haben wir automatische Builds verschiedener Varianten hinzugefügt – eine Zeitersparnis für alle.

Und schließlich unterstützen wir jetzt den Bootloader in der Anwendung und können die Anwendungen ohne Devkit aktualisieren.

Bleib dran und folge @ojousima und @ruuvicom auf Twitter für #FirmwareFriday-Beiträge!