
In diesem Teil des Tutorials verwenden wir Interrupts, um Bewegungen des Tags zu erkennen und gepufferte Samples vom Beschleunigungssensor zu lesen. Den fertigen Code dieses Blog-Beitrags kannst du bei Ruuvi GitHub im ruuviblog-Branch unter dem Tag 3.14.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.
Bewegungserkennung
Es gibt viele Fälle, in denen wir erkennen wollen, ob sich der RuuviTag bewegt. Vielleicht betreiben wir eine Asset-Tracking-Anwendung und möchten Daten schneller senden, während sich der Tag bewegt, oder wir möchten Ereignisse wie das Einschalten einer Maschine oder das Öffnen einer Tür erkennen.
Diese Erkennung könnte softwareseitig gelöst werden, aber unser Beschleunigungssensor LIS2DH12 verfügt bereits über die erforderliche Interrupt-Generierung, daher entscheiden wir uns für deren Nutzung.
Unser Tag erfährt eine konstante Beschleunigung von 1 G in Richtung Erdmittelpunkt, also die Schwerkraft. Diese Beschleunigungskomponente wird durch den im LIS2DH12 integrierten Hochpassfilter entfernt.
Datenpufferung
Ein weiterer Anwendungsfall für Interrupts ergibt sich bei Anwendungen, bei denen wir den Beschleunigungssensor in kurzen Intervallen abfragen wollen – zum Beispiel bei der Vibrationsmessung. Das Aufwecken der CPU für jeden Datenpunkt bei hoher Frequenz würde viel Strom verbrauchen. Wir können jedoch den integrierten First-in-First-out-Puffer (FIFO) im LIS2DH12 nutzen, um bis zu 32 Samples zu speichern und sie alle auf einmal auszulesen. Wir können einen Interrupt verwenden, um einen FIFO-Lesevorgang auszulösen, wenn der FIFO voll ist.
Schnittstelle
Unsere Sensor-Schnittstelle unterstützt keine Konfiguration generischer Interrupts, und wir möchten die Schnittstelle einfach halten. Daher werden wir nicht die Sensorschnittstelle selbst erweitern, sondern stattdessen unsere LIS2DH12–Schnittstelle. Das hat natürlich den Nachteil, dass die Anwendung an den LIS2DH12 gebunden ist, anstatt einen generischen Beschleunigungssensor-Treiber zu verwenden, aber andererseits können wir die Beschleunigungssensor-Schnittstelle jederzeit erweitern, falls Bedarf besteht.
Wir fügen der LIS2DH12-Schnittstelle 4 Funktionen hinzu:
/**
* Enable 32-level FIFO in LIS2DH12
* If FIFO is enabled, values are stored on LIS2DH12 FIFO and oldest element is returned on data read.
*
* parameter enable: true to enable FIFO, false to disable or reset FIFO.
* return: RUUVI_DRIVER_SUCCESS on success, error code from stack on error.
*/
ruuvi_driver_status_t ruuvi_interface_lis2dh12_fifo_use(const bool enable);
/**
* Read FIFO
* Reads up to num_elements data points from FIFO and populates pointer data with them
*
* parameter num_elements: Input: number of elements in data. Output: Number of elements placed in data
* parameter data: array of ruuvi_interface_acceleration_data_t with num_elements slots.
* return: RUUVI_DRIVER_SUCCESS on success
* return: RUUVI_DRIVER_ERROR_NULL if either parameter is NULL
* return: RUUVI_DRIVER_ERROR_INVALID_STATE if FIFO is not in use
* return: error code from stack on error.
*/
ruuvi_driver_status_t ruuvi_interface_lis2dh12_fifo_read(size_t* num_elements, ruuvi_interface_acceleration_data_t* data);
/**
* Enable FIFO full interrupt on LIS2DH12.
* Triggers as ACTIVE HIGH interrupt once FIFO has 32 elements.
*
* parameter enable: True to enable interrupt, false to disable interrupt
* return: RUUVI_DRIVER_SUCCESS on success, error code from stack otherwise.
**/
ruuvi_driver_status_t ruuvi_interface_lis2dh12_fifo_interrupt_use(const bool enable);
/**
* Enable activity interrupt on LIS2DH12
* Triggers as ACTIVE HIGH interrupt while detected movement is above threshold limit_g
* Axes are high-passed for this interrupt, i.e. gravity won't trigger the interrupt
* Axes are examined individually, compound acceleration won't trigger the interrupt.
*
* parameter enable: True to enable interrupt, false to disable interrupt
* parameter limit_g: Desired acceleration to trigger the interrupt.
* Is considered as "at least", the acceleration is rounded up to next value.
* Is written with value that was set to interrupt
* returns: RUUVI_DRIVER_SUCCESS on success
* returns: RUUVI_DRIVER_INVALID_STATE if acceleration limit is higher than maximum scale
* returns: error code from stack on error.
*
*/
ruuvi_driver_status_t ruuvi_interface_lis2dh12_activity_interrupt_use(const bool enable, float* limit_g);Die erste Funktion aktiviert den FIFO-Puffer, die zweite dient zum Auslesen der Daten aus dem FIFO, die dritte aktiviert den Interrupt bei vollem FIFO und die vierte aktiviert den Aktivitäts-Interrupt. Diese Funktionen sind stark vereinfachte Versionen der Funktionen, die der LIS2DH12 eigentlich ermöglichen würde, aber Einfachheit ist gut.
Treiber
Unser Treiber besteht im Wesentlichen aus fest programmierten Entscheidungen: Wir wählen den LIS2DH12-Interrupt 1 als unseren FIFO-Interrupt und Interrupt 2 als Aktivitäts-Interrupt. Obwohl der LIS2DH12 einen konfigurierbaren Interrupt-Level für den FIFO unterstützen würde, belassen wir den Interrupt bei einem festen Level von 32 Samples.
Dasselbe gilt für den Aktivitäts-Interrupt: Wir legen den Interrupt auf „Wenn die hochpassgefilterte Bewegung auf einer beliebigen Achse den Schwellenwert überschreitet“ fest. Der Schwellenwert ist konfigurierbar und wird vom Treiber automatisch von mg in Counts umgerechnet.
Testen
Während wir an den Sensoren arbeiten, fügen wir den Sensortreibern noch ein paar weitere Tests hinzu. Diesmal fügen wir Tests hinzu, um zu verifizieren, dass die Sensormodi wie erwartet funktionieren:
- Der Sensor muss nach der Initialisierung im SLEEP-Modus sein
- Der Sensor muss alle Werte als INVALID zurückgeben, wenn er vor dem ersten Sample ausgelesen wird
- Der Sensor muss in den SLEEP-Modus zurückkehren, nachdem der Modus auf SINGLE gesetzt wurde
- Der Sensor muss neue Daten haben, nachdem das Setzen des Modus auf SINGLE abgeschlossen ist
- Der Sensor muss bei aufeinanderfolgenden Aufrufen von DATA_GET nach einem SINGLE-Sample dieselben Werte einschließlich Zeitstempel liefern
- Der Sensor muss im CONTINUOUS-Modus bleiben, nachdem er auf kontinuierlich gesetzt wurde
- Der Sensor muss RUUVI_DRIVER_ERROR_INVALID_STATE zurückgeben, wenn er im kontinuierlichen Modus auf SINGLE gesetzt wird, und im kontinuierlichen Modus bleiben
- Der Sensor muss RUUVI_DRIVER_ERROR_NULL zurückgeben, wenn ein Null-Modus als Parameter übergeben wird
- Der Sensor muss im CONTINUOUS-Modus aktualisierte Daten zurückgeben

Wieder einmal stellen wir fest, dass es zahlreiche Grenzfälle gibt, die unsere Treiber noch nicht handhaben – zum Beispiel geben unsere Treiber nach dem init vor Beginn der Messung keine explizit ungültigen Werte zurück. Diese Probleme zu beheben ist einfach; wir spüren die einzelnen Fehler auf, wie wir es in Teil 8 getan haben.
Aufgaben
Diesmal müssen wir task_acceleration und task_advertisement anpassen. Konkret fügen wir Interrupt-Funktionen zu unserem task_accelerometer hinzu und aktivieren die LIS2DH12-Interrupts.
Unser FIFO-Task gibt den Puffer aus und unser Bewegungs-Task erhöht den Bewegungszähler. Wir fügen außerdem eine Funktion hinzu, um den Bewegungszähler abzurufen. Der folgende Ausschnitt enthält die Ergänzungen.
static void task_acceleration_fifo_full_task(void *p_event_data, uint16_t event_size)
{
ruuvi_driver_status_t err_code = RUUVI_DRIVER_SUCCESS;
ruuvi_interface_acceleration_data_t data[32];
size_t data_len = sizeof(data);
err_code |= ruuvi_interface_lis2dh12_fifo_read(&data_len, data);
char msg[APPLICATION_LOG_BUFFER_SIZE] = { 0 };
snprintf(msg, sizeof(msg), "%u: Read %u data points\r\n", (uint32_t)ruuvi_platform_rtc_millis(), data_len);
ruuvi_platform_log(RUUVI_INTERFACE_LOG_INFO, msg);
/*for(int ii = 0; ii < data_len; ii++)
{
memset(msg, 0, sizeof(msg));
snprintf(msg, sizeof(msg), "T: %lu; X: %.3f; Y: %.3f; Z: %.3f;\r\n", (uint32_t)(data[ii].timestamp_ms&0xFFFFFFFF), data[ii].x_g, data[ii].y_g, data[ii].z_g);
ruuvi_platform_log(RUUVI_INTERFACE_LOG_DEBUG, msg);
ruuvi_platform_delay_ms(1);
}
*/
RUUVI_DRIVER_ERROR_CHECK(err_code, RUUVI_DRIVER_SUCCESS);
}
static void on_fifo (ruuvi_interface_gpio_evt_t event)
{
ruuvi_platform_scheduler_event_put(NULL, 0, task_acceleration_fifo_full_task);
}
static void on_movement (ruuvi_interface_gpio_evt_t event)
{
ruuvi_platform_log(RUUVI_INTERFACE_LOG_DEBUG, "Activity\r\n");
m_nbr_movements++;
}
#if RUUVI_BOARD_ACCELEROMETER_LIS2DH12_PRESENT
err_code = RUUVI_DRIVER_SUCCESS;
// Only SPI supported for now
bus = RUUVI_DRIVER_BUS_SPI;
handle = RUUVI_BOARD_SPI_SS_ACCELEROMETER_PIN;
err_code |= ruuvi_interface_lis2dh12_init(&acceleration_sensor, bus, handle);
RUUVI_DRIVER_ERROR_CHECK(err_code, RUUVI_DRIVER_ERROR_NOT_FOUND);
if(RUUVI_DRIVER_SUCCESS == err_code)
{
err_code |= task_acceleration_configure();
err_code |= ruuvi_interface_lis2dh12_fifo_use(true);
err_code |= ruuvi_interface_lis2dh12_fifo_interrupt_use(true);
float ths = APPLICATION_ACCELEROMETER_ACTIVITY_THRESHOLD;
err_code |= ruuvi_interface_lis2dh12_activity_interrupt_use(true, &ths);
// Let pins settle
ruuvi_platform_delay_ms(10);
// Setup FIFO and activity interrupts
err_code |= ruuvi_platform_gpio_interrupt_enable(RUUVI_BOARD_INT_ACC1_PIN, RUUVI_INTERFACE_GPIO_SLOPE_LOTOHI, RUUVI_INTERFACE_GPIO_MODE_INPUT_NOPULL, on_fifo);
err_code |= ruuvi_platform_gpio_interrupt_enable(RUUVI_BOARD_INT_ACC2_PIN, RUUVI_INTERFACE_GPIO_SLOPE_LOTOHI, RUUVI_INTERFACE_GPIO_MODE_INPUT_NOPULL, on_movement);
char msg[APPLICATION_LOG_BUFFER_SIZE] = { 0 };
snprintf(msg, sizeof(msg), "Configured interrupt threshold at %.3f mg\r\n", ths);
ruuvi_platform_log(RUUVI_INTERFACE_LOG_INFO, msg);
return err_code;
}
ruuvi_driver_status_t task_acceleration_movement_count_get(uint8_t * const count)
{
*count = m_nbr_movements;
return RUUVI_DRIVER_SUCCESS;
}
#endifBeachte die unterschiedliche Handhabung der Ereignisse: Das Erhöhen eines Zählers im Aktivitäts-Interrupt ist ein schnelles Ereignis, das wir im Interrupt-Kontext ausführen können. Das Auslesen und Ausgeben des FIFO nimmt jedoch Zeit in Anspruch, daher werden wir (schedule) die Aktion für einen späteren Zeitpunkt planen.
Jetzt bestehen unsere Tests und der FIFO wird im Terminal ausgegeben. Wenn du den fertigen Code ausführst, beachte bitte, dass die FIFO-Ausgabe auf dem Level DEBUG erfolgt, und passe deinen Log-Level entsprechend an.

Datenformat 5
Der fehlende Teil für das Datenformat 5 war die Bewegungserkennung. Wir fügen ruuvi_endpoint_5 hinzu, das die erforderlichen Daten als Input-Struct entgegennimmt und die Daten in den angegebenen Puffer kodiert.

Das Problem mit der Spannungsauslesung stellte sich als einfaches Versäumnis bei der Kodierung heraus. Ein weiterer Punkt für die Tests: Überprüfen, ob die Kodierungsfunktionen die erwarteten Ergebnisse liefern.
Stromverbrauchsprofil
Wir können einen deutlich erhöhten Stromverbrauch erwarten, da wir den Beschleunigungssensor mit 10 Hz statt der bisherigen 1 Hz betreiben und zudem FIFO-Auslesungen durchführen. Schauen wir es uns an.

Unser letztes Ergebnis lag etwas unter 23 μA, jetzt haben wir unseren Verbrauchswert um etwa 7,5 μA erhöht. Eine Erhöhung des Stromverbrauchs um 33 % für eine FIFO-Auslesung, die für nichts weiter verwendet wird, ist nicht wirklich sinnvoll, daher werden wir ein Compiler-Flag hinzufügen, um den FIFO in Zukunft optional zu machen.
Fazit
Wir haben nun die Grundlagen für Anwendungen mit dem Beschleunigungssensor geschaffen. Wir haben jedoch erst an der Oberfläche der Möglichkeiten gekratzt, die Beschleunigungsdaten bieten. Einer der wichtigsten fehlenden Bausteine ist das schnelle und zuverlässige Auslesen der Daten. Dies wird bald mit dem GATT-Profil möglich sein.
Bleib dran und folge @ojousima und @ruuvicom auf Twitter für #FirmwareFriday-Beiträge!