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

Ruuvi Firmware – Teil 10: NFC-Auslesen

Ruuvi Firmware Serie Teil 10 Intro-Bild

In diesem Teil des Tutorials fügen wir dem Tag eine Nahfeldkommunikations-Funktion (NFC) zum Auslesen hinzu, d. h. RuuviTag wird am Ende dieses Beitrags ein schreibgeschützter NFC-Tag sein. Der finale Code dieses Blogbeitrags kann auf Ruuvi GitHub im ruuviblog-Branch, Tag 3.10.0-alpha, heruntergeladen werden.

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.

ruuvi.firmware.c Architektur 3.10.0
Dieses Bild wird langsam voll – Zeit, es für den nächsten Teil neu zu zeichnen 🙂

NFC

NFC-Interface

Wie im vorherigen Beitrag werden wir nicht alles auf einmal implementieren. Bei der Bluetooth Low-Energy-Werbung (BLE) haben wir nur das Senden von Advertisements implementiert und das Scannen für später gelassen. Ebenso lassen wir NFC-Schreibvorgänge auf den Tag vorerst unimplementiert.

Wir wollen die Funktionalität aus Ruuvi FW 1.2.12 nachbilden, die die Firmware-Version, die Media-Access-Control-Adresse (MAC) und die eindeutige ID des Tags ausgibt. Da unser Kommunikations-Interface kein Konzept von „Slots“ kennt, wie z. B. ndef-records, fügen wir die Text-records als NFC-spezifische Funktionen hinzu und senden sie als NFC-binären record.

/*
 * Initializes NFC hardware
 *
 * Returns RUUVI_DIRVER_SUCCESS on success, RUUVI_DIRVER_ERROR_INVALID_STATE if radio is already initialized
 */
ruuvi_driver_status_t ruuvi_interface_communication_nfc_init(ruuvi_interface_communication_t* const channel);

/*
 * Uninitializes NFC hardware
 *
 * Returns RUUVI_DIRVER_SUCCESS on success or if radio was not initialized.
 * Returns RUUVI_DRIVER_ERROR_INVALID_STATE if radio hardware was initialized by another radio module.
 */
ruuvi_driver_status_t ruuvi_interface_communication_nfc_uninit(ruuvi_interface_communication_t* const channel);

// Encodes the given data fields into NFC buffer. Clears previous data.
ruuvi_driver_status_t ruuvi_interface_communication_nfc_data_set(void);

/**
 * Send data as ascii-encoded binary.
 *
 * Returns RUUVI_DRIVER_SUCCESS if the data was placed in buffer
 * Returns error code from the stack if data could not be placed to the buffer
 */
ruuvi_driver_status_t ruuvi_interface_communication_nfc_send(ruuvi_interface_communication_message_t* messge);

/**
 * Send data as ascii-encoded binary.
 *
 * Returns RUUVI_DRIVER_SUCCESS if the data was placed in buffer
 * Returns error code from the stack if data could not be placed to the buffer
 */
ruuvi_driver_status_t ruuvi_interface_communication_nfc_send(ruuvi_interface_communication_message_t* messge);

/**
 * Sets the device firmware version into "FW" text field.
 *
 *  parameter version: Pointer on string representation of the version. ie. "FW: ruuvi.firmware.c 3.10.0"
 *
 * returns RUUVI_DRIVER_SUCCESS on success
 * returns RUUVI_DRIVER_ERROR_NULL if version is NULL and length != 0
 * returns RUUVI_DRIVER_INVALID_LENGTH if name is over 32 bytes long
 */
ruuvi_driver_status_t ruuvi_interface_communication_nfc_fw_version_set(const uint8_t* const version, const uint8_t length);

/**
 * Sets the device mac address into "ad" text field.
 *
 * returns RUUVI_DRIVER_SUCCESS on success
 * returns RUUVI_DRIVER_ERROR_NULL if address is NULL and length != 0
 * returns RUUVI_DRIVER_INVALID_LENGTH if name is over 32 bytes long
 */
ruuvi_driver_status_t ruuvi_interface_communication_nfc_address_set(const uint8_t* const address, const uint8_t length);

/**
 * Sets the device id into "id" text field. Set NULL/0 to disable
 *
 *  parameter id: Pointer on string representation of the id. ie. "ID: 12:34:56:78:90:AB:CD:EF
 *
 * returns RUUVI_DRIVER_SUCCESS on success
 * returns RUUVI_DRIVER_ERROR_NULL if id is NULL and length != 0
 * returns RUUVI_DRIVER_INVALID_LENGTH if name is over 32 bytes long
 */
ruuvi_driver_status_t ruuvi_interface_communication_nfc_id_set(const uint8_t* const id, const uint8_t length);

// Not implemented
ruuvi_driver_status_t ruuvi_interface_communication_nfc_receive(ruuvi_interface_communication_message_t* messge);

Rohdaten anzeigen

NFC-Treiber

Der Nordic-NFC-Treiber richtet einen Puffer im RAM ein, und wenn das NFC-Feld erkannt wird, wird dieser Puffer über NFC an den Leser übertragen – oder er wird vom Schreibgerät auf einen neuen Wert geändert.

Daher benötigen wir Double-Buffering: Zuerst richten wir ein, was wir in Records senden möchten, und dann werden diese Records in den NFC-Puffer codiert. Wenn das NFC-Feld gerade aktiv ist, ändern wir den Puffer nicht.

Der Treiber selbst besteht aus einfachen Wrappern um die Software Development Kit-Funktionen (SDK) und der Handhabung der Datenpuffer. Es gibt eine Besonderheit: Wir müssen HAL_NFC_ENGINEERING_BC_FTPAN_WORKAROUND zu unseren Compiler-Flags hinzufügen, damit der NFC-Treiber den Hochfrequenztakt (HFCLK) nach Unterbrechung der NFC-Verbindung freigibt.

Integration in die Anwendung

Die Integration läuft wie gewohnt: Wir schreiben task_nfc und rufen die Initialisierung in main.c auf. Die Initialisierung richtet die Records ein und wir sind bereit.

/**
 * Initializes NFC and configures FW, ADDR and ID records according to application_config.h constants
 *
 * return RUUVI_DRIVER_SUCCESS on success
 * return error code from stack on error
 */
ruuvi_driver_status_t task_nfc_init(void);

/**
 * Sets given message to NFC RAM buffer. Clears previous message
 *
 * return RUUVI_DRIVER_SUCCESS on success
 * return error code from stack on error
 */
ruuvi_driver_status_t task_nfc_send(ruuvi_interface_communication_message_t* message);

Rohdaten anzeigen

Bisher brauchen wir nur die Initialisierung.

int main(void)
{
  // Init logging
  ruuvi_driver_status_t status = RUUVI_DRIVER_SUCCESS;
  status |= ruuvi_platform_log_init(APPLICATION_LOG_LEVEL);
  RUUVI_DRIVER_ERROR_CHECK(status, RUUVI_DRIVER_SUCCESS);

  // Init yield
  status |= ruuvi_platform_yield_init();
  RUUVI_DRIVER_ERROR_CHECK(status, RUUVI_DRIVER_SUCCESS);

  // Init GPIO
  status |= ruuvi_platform_gpio_init();
  RUUVI_DRIVER_ERROR_CHECK(status, RUUVI_DRIVER_SUCCESS);

  // Initialize LED gpio pins, turn RED led on.
  status |= task_led_init();
  status |= task_led_write(RUUVI_BOARD_LED_RED, TASK_LED_ON);
  RUUVI_DRIVER_ERROR_CHECK(status, RUUVI_DRIVER_SUCCESS);

  // Initialize SPI
  status |= task_spi_init();
  RUUVI_DRIVER_ERROR_CHECK(status, RUUVI_DRIVER_SUCCESS);

  // Initialize RTC
  status |= task_rtc_init();
  RUUVI_DRIVER_ERROR_CHECK(status, RUUVI_DRIVER_SUCCESS);

  // Initialize power
  status |= task_power_dcdc_init();
  RUUVI_DRIVER_ERROR_CHECK(status, RUUVI_DRIVER_SUCCESS);

  // Initialize nfc
  status |= task_nfc_init();
  RUUVI_DRIVER_ERROR_CHECK(status, RUUVI_DRIVER_SUCCESS);

  #if RUUVI_RUN_TESTS
  // Tests will initialize and uninitialize the sensors, run this before using them in application
  test_sensor_run();

  // Print unit test status, activate tests by building in DEBUG configuration under SES
  size_t tests_run, tests_passed;
  test_sensor_status(&tests_run, &tests_passed);
  char message[128] = {0};
  snprintf(message, sizeof(message), "Tests ran: %u, passed: %u\r\n", tests_run, tests_passed);
  ruuvi_platform_log(RUUVI_INTERFACE_LOG_INFO, message);
  #endif

  // Initialize BLE
  status |= task_advertisement_init();
  RUUVI_DRIVER_ERROR_CHECK(status, RUUVI_DRIVER_SUCCESS);

  // Initialize ADC
  status |= task_adc_init();
  RUUVI_DRIVER_ERROR_CHECK(status, RUUVI_DRIVER_SUCCESS);

  // Initialize environmental- nRF52 will return ERROR NOT SUPPORTED if
  // DSP was configured, log warning
  status |= task_environmental_init();
  RUUVI_DRIVER_ERROR_CHECK(status, RUUVI_DRIVER_ERROR_NOT_SUPPORTED);

  // Allow NOT FOUND in case we're running on basic model, NOT_SUPPORTED in case we have previous error from BME280
  status |= task_acceleration_init();
  RUUVI_DRIVER_ERROR_CHECK(status, RUUVI_DRIVER_ERROR_NOT_FOUND | RUUVI_DRIVER_ERROR_NOT_SUPPORTED);

  // Initialize button with on_button task, do not reset on errors from model Basic
  status |= task_button_init(RUUVI_INTERFACE_GPIO_SLOPE_HITOLO, task_button_on_press);
  RUUVI_DRIVER_ERROR_CHECK(status, RUUVI_DRIVER_ERROR_NOT_FOUND | RUUVI_DRIVER_ERROR_NOT_SUPPORTED);

  // Turn RED led off. Turn GREEN LED on if no errors occured
  status |= task_led_write(RUUVI_BOARD_LED_RED, TASK_LED_OFF);
  if(RUUVI_DRIVER_SUCCESS == status)
  {
    status |= task_led_write(RUUVI_BOARD_LED_GREEN, TASK_LED_ON);
    ruuvi_platform_delay_ms(1000);
  }
  // Reset any previous errors, turn LEDs off, continue unless fatal error occurs
  status |= task_led_write(RUUVI_BOARD_LED_GREEN, TASK_LED_OFF);
  RUUVI_DRIVER_ERROR_CHECK(status, ~RUUVI_DRIVER_ERROR_FATAL);

  while (1)
  {
    status = ruuvi_platform_yield();
    // Reset only on fatal error
    RUUVI_DRIVER_ERROR_CHECK(status, ~RUUVI_DRIVER_ERROR_FATAL);
  }
}

Rohdaten anzeigen

Wir haben auch eine Power-Task hinzugefügt, um den DC/DC-Wandler auf dem RuuviTag zu initialisieren

Schauen wir uns das mit dem NFC-Lesegerät des Smartphones an:

NFC-Tools
NFC sieht gut aus

Backlog

Power-Interface

Im vorherigen Beitrag haben wir übermäßig viel Strom verbraucht, weil der interne Gleichspannungswandler (DC/DC) des nRF52832 nicht aktiviert war. Wir fügen ein Interface hinzu, das eine Liste von zu aktivierenden Reglern übernimmt und die Liste an die Plattform-Treiber weitergibt, die dann die ausgewählten Regler aktivieren.

Es gibt weitere stromrelevante Funktionen, wie das Eintreten in den Tiefschlaf und die Konfiguration der Aufwachvorgänge. Diese werden wir jedoch noch nicht implementieren.

#define RUUVI_INTERFACE_POWER_REGULATORS_DISABLED 0
#define RUUVI_INTERFACE_POWER_REGULATORS_DCDC_INTERNAL (1<<0)  // DC/DC for internal circuitry, i.e. nRF52832 radio
#define RUUVI_INTERFACE_POWER_REGULATORS_DCDC_HV       (1<<1)  // DC/DC for high voltage, i.e. nRF52840 USB

typedef uint32_t ruuvi_interface_power_regulators_t;

/**
 * Enable given regulators. The implementation must work regardless of software radio state, i.e. 
 * on S132 on nRF52 the function must check if softdevice is running and call softdevice wrapper to
 * DC/DC if it is and write registers directly if SD is not running. 
 *
 * parameter regulators: binary flags of regulators to enable. 
 * return: RUUVI_DRIVER_SUCCESS on success, error code from stack in case of a error.
 */
ruuvi_driver_status_t ruuvi_interface_power_regulators_enable(const ruuvi_interface_power_regulators_t regulators);

Rohdaten anzeigen

Power-Treiber

Der Power-Treiber ist an dieser Stelle einfach. Wir erstellen die Konfiguration und deinitialisieren den Power-Treiber, falls er bereits initialisiert war, dann konfigurieren und initialisieren wir den Treiber.

static bool m_is_init = false;

ruuvi_driver_status_t ruuvi_interface_power_regulators_enable(const ruuvi_interface_power_regulators_t regulators)
{

  ret_code_t err_code = NRF_SUCCESS;
  nrfx_power_config_t config = {0};
  if(RUUVI_INTERFACE_POWER_REGULATORS_DCDC_INTERNAL & regulators)
  {
    config.dcdcen = true;
  }
  if(RUUVI_INTERFACE_POWER_REGULATORS_DCDC_HV & regulators)
  {
    #if NRF_POWER_HAS_VDDH
    config.dcdcenhv = true;
    #endif
  }
  if(m_is_init)
  {
    nrfx_power_uninit();
    m_is_init = false;
  }
  err_code |= nrfx_power_init (&config);
  m_is_init = true;
  return ruuvi_platform_to_ruuvi_error(&err_code);
}

Rohdaten anzeigen

Kommunikations-Interface

Auch wenn wir noch nichts mit den NFC-Verbindungsinformationen machen, möchten wir vielleicht das NFC-Verbindungsereignis nutzen, um in einen konfigurierbaren Modus zu wechseln (oder etwas Ähnliches). Erweitern wir das Kommunikations-Interface um die Ereignisse connected, disconnected, sent, received und fügen einen Event-Handler zum Function-Pointer-Interface hinzu.

// Standard BLE Broadcast manufacturer specific data payload length
#define RUUVI_INTERFACE_COMMUNICATION_MESSAGE_MAX_LENGTH 24

typedef struct{
  uint8_t data[RUUVI_INTERFACE_COMMUNICATION_MESSAGE_MAX_LENGTH];
  uint8_t data_length;
  bool repeat;
}ruuvi_interface_communication_message_t;

typedef enum {
  RUUVI_INTERFACE_COMMUNICATION_CONNECTED,
  RUUVI_INTERFACE_COMMUNICATION_DISCONNECTED,
  RUUVI_INTERFACE_COMMUNICATION_SENT,
  RUUVI_INTERFACE_COMMUNICATION_RECEIVED,
}ruuvi_interface_communication_evt_t;

typedef struct ruuvi_interface_communication_t ruuvi_interface_communication_t;          // forward declaration *and* typedef
typedef ruuvi_driver_status_t(*ruuvi_interface_communication_xfer_fp_t)(ruuvi_interface_communication_message_t*);
typedef ruuvi_driver_status_t(*ruuvi_interface_communication_init_fp_t)(ruuvi_interface_communication_t* const);
typedef ruuvi_driver_status_t(*ruuvi_interface_communication_evt_handler_fp_t)(const ruuvi_interface_communication_evt_t);

// Every Ruuvi communication channel must  be able to send data and receive data.
// Channels can be init or uninit
struct ruuvi_interface_communication_t
{
  ruuvi_interface_communication_xfer_fp_t        send;
  ruuvi_interface_communication_xfer_fp_t        read;
  ruuvi_interface_communication_init_fp_t        init;
  ruuvi_interface_communication_init_fp_t        uninit;
  ruuvi_interface_communication_evt_handler_fp_t on_evt;
};

/**
 * Writes maximum 64-bit unique id of the device to the pointer. This ID
 * must remain same across reboots and reflashes of the device
 *
 * param id: Output, value of id.
 * return RUUVI_DRIVER_SUCCESS on success
 * return RUUVI_DRIVER_ERROR_NOT_SUPPORTED if ID cannot be returned on given platform
 *
 */
ruuvi_driver_status_t ruuvi_interface_communication_id_get(uint64_t* const id);

Rohdaten anzeigen

Die brauchen wir später, wenn wir die Nachrichten ordentlich in eine Warteschlange stellen, statt „lass fallen, was gerade gesendet wird, und fang an, das hier zu senden“.

Wir fügen auch eine Funktion hinzu, um die eindeutige ID des Geräts im Kommunikations-Interface zurückzugeben, und eine Funktion, um die MAC-Adresse im Radio-Interface zurückzugeben.

Stromverbrauch

Wieder ist es Zeit zu prüfen, wie der Stromverbrauch aussieht. Schauen wir uns zunächst den Grundpegel beim Booten an.

nRF Power Profile 2.5.0
Nichts Besonderes hier

Jetzt verbrauchen wir im Leerlauf 5,9 μA, 0,7 μA weniger als die 6,6 μA in unserem vorherigen Beitrag. Wir verbrauchen wahrscheinlich nicht wirklich weniger Strom als zuvor, sondern dies ist eher ein Temperatureffekt oder eine Frage der Messwiederholbarkeit.

Als Nächstes scannen wir den Tag und prüfen, wie der Verbrauch aussieht.

Verbrauch während des NFC-Scans
Verbrauch während des NFC-Scans

Der Stromverbrauch ist während des Scans deutlich höher, da der HFCLK läuft, um den Tag zu betreiben. Wichtig zu prüfen ist, dass wir nach dem Scan wieder auf dem vorherigen Niveau sind, d. h. das C-Flag hat den Workaround für übermäßigen Stromverbrauch nach dem NFC-Scan aktiviert.

Als Nächstes drücken wir „B“, um das Funkmodul zu starten, und schauen, wie sich unser Power-Treiber schlägt.

BLE-Übertragung
Jetzt verbraucht unsere BLE-Übertragung nur noch 10 mA in der Spitze

Jetzt liegt unser durchschnittlicher Stromverbrauch bei 17,4 μA, 10 μA weniger als im vorherigen Beitrag, wo DC/DC nicht aktiviert war. Das ist auch sehr konkurrenzfähig mit den 24 μA der aktuellen Ruuvi Firmware – vielleicht bekommen wir am Ende eine etwas energieeffizientere Firmware?

Fazit

Wir haben jetzt fast die gesamte Funktionalität der ursprünglichen Ruuvi Firmware, aber ein wichtiges Feature fehlt noch: die Aktualisierung der Messung in regelmäßigen Abständen. Im nächsten Teil der Serie fügen wir einen Scheduler hinzu, um die Daten in regelmäßigen Abständen zu aktualisieren.

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