
Heute gehen wir durch, wie man einen neuen Sensor zur Ruuvi-Firmware hinzufügt. Dieser Beitrag baut auf der RuuviFW-Version 3.28.0 auf, die auf GitHub verfügbar ist. Das Projekt wird mit Segger Embedded Studio Nordic Semiconductor Version 4.18 kompiliert. Die nRF SDK-Version ist nRF5_SDK_15.3.0_59ac345. Detaillierte Einrichtungsanweisungen findest du im Beitrag Ruuvi-Firmware – Teil 1.

Sensor
Der Sensor, den wir hinzufügen, ist TMP117 von Texas Instruments. Es ist ein hochpräziser Temperatursensor mit ±0,1 °C absoluter Genauigkeit von –20 °C bis +50 °C.
Lass uns anfangen
Wir arbeiten in diesem Blogbeitrag an einem noch nicht öffentlichen Board mit dem Codenamen „Kaarle“.

Low-Level-Schnittstelle: I2C-Bus
Um die Daten tatsächlich vom Sensor zu erhalten, müssen wir die I2C-Schreib- und Lesefunktionen implementieren. Der Großteil der Arbeit ist bereits erledigt, wir fügen einfach Aufrufe zu den Ruuvi-I2C-Bibliotheken hinzu, die wiederum das zugrunde liegende Software Development Kit (SDK) aufrufen. Die Arbeit wird durch die TMP117-Registerstruktur noch weiter vereinfacht, die Lese-/Schreibbefehle mit fester Größe hat.
Um die Projektstruktur sauber zu halten, erstellen wir neue Dateien:
ruuvi.drivers.c/interfaces/i2c/ruuvi_interface_i2c_tmp117.c
ruuvi.drivers.c/interfaces/i2c/ruuvi_interface_i2c_tmp117.hund platzieren die Funktionalität dort. Du findest den Abschnitt im rechten grünen Kasten des Architekturdiagramms.
/**
* @brief I2C write function for TMP117
*
*
* @param[in] dev_id @ref I2C interface handle, i.e. I2C addess of TMP117
* @param[in] reg_addr TMP117 register address to write.
* @param[in] reg_val 16-bit value to be written
* @return RUUVI_DRIVER_SUCCESS on success
* @return RUUVI_DRIVER_ERROR_TIMEOUT if device does not respond on bus
**/
ruuvi_driver_status_t ruuvi_interface_i2c_tmp117_write(const uint8_t dev_id, const uint8_t reg_addr,
const uint16_t reg_val)
{
uint8_t command[3];
command[0] = reg_addr;
command[1] = reg_val >> 8;
command[2] = reg_val & 0xFF
return ruuvi_interface_i2c_write_blocking(dev_id, command, sizeof(command), true);
}
/**
* @brief I2C Read function for TMP117
*
* Binds Ruuvi Interface I2C functions for TMP117
*
* @param[in] dev_id @ref I2C interface handle, i.e. I2C addess of TMP117.
* @param[in] reg_addr TMP117 register address to read.
* @param[in] reg_val pointer to 16-bit data to be received.
* @return RUUVI_DRIVER_SUCCESS on success
* @return RUUVI_DRIVER_ERROR_TIMEOUT if device does not respond on bus
**/
ruuvi_driver_status_t ruuvi_interface_i2c_tmp117_read(const uint8_t dev_id, const uint8_t reg_addr,
uint16_t* const reg_val)
{
if(NULL == p_reg_data) { return RUUVI_DRIVER_ERROR_NULL; }
ruuvi_driver_status_t err_code = RUUVI_DRIVER_SUCCESS;
uint8_t command[3] = {0};
command[0] = reg_addr;
err_code |= ruuvi_interface_i2c_write_blocking(dev_id, command, 1, false);
err_code |= ruuvi_interface_i2c_read_blocking(dev_id, &(command[1]), len);
reg_val = (command[1] << 8) + command[2];
return err_code;
}Die I2C-Pins sind bereits in ruuvi_board_kaarle.h für die SHTC3-Unterstützung definiert. Wir fügen die I2C-Adresse von TMP117 mit ADD0-Pin auf Masse zur Header-Datei hinzu:
#define RUUVI_BOARD_TMP117_I2C_ADDRESS 0x48Die Board-Definitionen befinden sich in der Mitte links im Anwendungsdiagramm.
TMP117-Schnittstelle
Wir trennen die Logik für den Zugriff auf TMP117 vom tatsächlichen Datenverkehr auf dem I2C-Bus und erstellen neue Dateien zur Steuerung des Sensors:
ruuvi.drivers.c/interfaces/environmental/ruuvi_interface_tmp117.c
ruuvi.drivers.c/interfaces/environmental/ruuvi_interface_tmp117.cDiese Dateien befinden sich im linken grünen Abschnitt des Architekturdiagramms. Wir könnten einen Treiber schreiben, der vollständig von Ruuvi-Treibern entkoppelt ist und die I2C-Schnittstelle als Funktionszeiger übernimmt, aber das würde aus Sicht des Ruuvi-Treibers keinen wirklichen Mehrwert bieten, also codieren wir die I2C-Zugriffsfunktionen einfach fest ein.
Registerzugriff von TMP117
Nachdem die I2C-Kommunikation geklärt ist, schreiben wir die Registerkarte und Funktionen für den Zugriff auf die Register aus. Die Registerkarte selbst ist wirklich einfach mit nur 10 16-Bit-Registern.
ADDRESS TYPE RESET ACRONYM REGISTER NAME
00h R 8000h Temp_Result Temperature result register
01h R/W 0220h Configuration Configuration register
02h R/W 6000h THigh_Limit Temperature high limit register
03h R/W 8000h TLow_Limit Temperature low limit register
04h R/W 0000h EEPROM_UL EEPROM unlock register
05h R/W xxxxh EEPROM1 EEPROM1 register
06h R/W xxxxh EEPROM2 EEPROM2 register
07h R/W 0000h Temp_Offset Temperature offset register
08h R/W xxxxh EEPROM3 EEPROM3 register
0Fh R 0117h Device_ID Device ID registerDann implementieren wir die minimal erforderliche Konfiguration zum Überprüfen der ID über I2C, zum Durchführen eines Soft-Resets, zum Konfigurieren des Oversampling-Verhältnisses, zum Konfigurieren des Konvertierungsintervalls, zum Auslösen von Einzel- und kontinuierlichen Messungen und zum Auslesen des Temperaturwerts.
#include "ruuvi_driver_error.h"
#include "ruuvi_driver_sensor.h"
#include "ruuvi_interface_tmp117.h"
#include "ruuvi_interface_i2c_tmp117.h"
static uint8_t m_address;
static uint16_t ms_per_sample;
static ruuvi_driver_status_t tmp117_soft_reset(void)
{
uint16_t reset = TMP117_MASK_RESET & 0xFFFF;
return ruuvi_interface_i2c_tmp117_write(m_address, TMP117_REG_CONFIGURATION, reset);
}
static ruuvi_driver_status_t tmp117_validate_id(void)
{
uint16_t id;
ruuvi_interface_i2c_tmp117_read(m_address, TMP117_REG_DEVICE_ID, &id);
id &= TMP117_MASK_ID;
return (TMP117_VALUE_ID == id)? RUUVI_DRIVER_SUCCESS : RUUVI_DRIVER_ERROR_NOT_FOUND;
}
static ruuvi_driver_status_t tmp117_oversampling_set(const uint8_t num_os)
{
uint16_t reg_val;
ruuvi_driver_status_t err_code;
err_code = ruuvi_interface_i2c_tmp117_read(m_address, TMP117_REG_CONFIGURATION, ®_val);
reg_val &= ~TMP117_MASK_OS;
switch(num_os)
{
case 1:
reg_val |= TMP117_VALUE_OS_1;
ms_per_sample = 16;
break;
case 8:
reg_val |= TMP117_VALUE_OS_8;
ms_per_sample = 125;
break;
case 32:
reg_val |= TMP117_VALUE_OS_32;
ms_per_sample = 500;
break;
case 64:
reg_val |= TMP117_VALUE_OS_64;
ms_per_sample = 1000;
break;
default:
return RUUVI_DRIVER_ERROR_INVALID_PARAM;
}
err_code |= ruuvi_interface_i2c_tmp117_write(m_address, TMP117_REG_CONFIGURATION, reg_val);
return err_code;
}
static ruuvi_driver_status_t tmp117_sleep(void)
{
uint16_t reg_val;
ruuvi_driver_status_t err_code;
err_code = ruuvi_interface_i2c_tmp117_read(m_address, TMP117_REG_CONFIGURATION, ®_val);
reg_val &= ~TMP117_MASK_MODE;
reg_val |= TMP117_VALUE_MODE_SLEEP;
err_code |= ruuvi_interface_i2c_tmp117_write(m_address, TMP117_REG_CONFIGURATION, reg_val);
return err_code;
}
static ruuvi_driver_status_t tmp117_sample(void)
{
uint16_t reg_val;
ruuvi_driver_status_t err_code;
err_code = ruuvi_interface_i2c_tmp117_read(m_address, TMP117_REG_CONFIGURATION, ®_val);
reg_val &= ~TMP117_MASK_MODE;
reg_val |= TMP117_VALUE_MODE_SINGLE;
err_code |= ruuvi_interface_i2c_tmp117_write(m_address, TMP117_REG_CONFIGURATION, reg_val);
return err_code;
}
static ruuvi_driver_status_t tmp117_continuous(void)
{
uint16_t reg_val;
ruuvi_driver_status_t err_code;
err_code = ruuvi_interface_i2c_tmp117_read(m_address, TMP117_REG_CONFIGURATION, ®_val);
reg_val &= ~TMP117_MASK_MODE;
reg_val |= TMP117_VALUE_MODE_CONT;
err_code |= ruuvi_interface_i2c_tmp117_write(m_address, TMP117_REG_CONFIGURATION, reg_val);
return err_code;
}
static float tmp117_read(void)
{
uint16_t reg_val;
ruuvi_driver_status_t err_code;
err_code = ruuvi_interface_i2c_tmp117_read(m_address, TMP117_REG_TEMP_RESULT, ®_val);
int16_t temperature = (reg_val > 32767)? reg_val - 65535 : reg_val;
float temperature = (0.0078125 * temperature);
if(TMP117_VALUE_TEMP_NA == reg_val || RUUVI_DRIVER_SUCCESS != err_code) { temperature = NAN; }
return temperature;
}High-Level-Zugriff auf TMP117
Die Ruuvi-Firmware erwartet, dass die Treiber eine einheitliche Schnittstelle zu den Treibern bereitstellen. Auf diese Weise können wir das Backend, das Daten bereitstellt, austauschen, ohne die Anwendungslogik überarbeiten zu müssen. Lass uns die erforderlichen Funktionen über Low-Level-Zugriffsfunktionen implementieren.
Initialisierung
Die Initialisierung bereitet den Sensor für die Verwendung vor, führt alle Selbsttests durch und versetzt den Sensor in den niedrigstmöglichen Stromzustand. Die Initialisierung sperrt den Sensor auch für andere Benutzer, bis er deinitialisiert wurde.
/**
* @brief Initialize and uninitialize sensor.
* Init and uninit will setup sensor with function pointers.
* The sensor wil be initialized to lowest power state possible.
*
* @param[in,out] p_sensor pointer to sensor structure
* @param[in] bus bus to use, i.r. I2C or SPI
* @param[in] handle for the sensor, for example I2C address or SPI chip select pin
* @return @c RUUVI_DRIVER_SUCCESS on success
* @return @c RUUVI_DRIVER_ERROR_NULL if p_sensor is NULL
* @return @c RUUVI_DRIVER_ERROR_NOT_FOUND if there is no response from sensor or if ID of
* a sensor read over bus does not match expected value
* @return @c RUUVI_DRIVER_ERROR_SELFTEST if sensor is found but it does not pass selftest
* @return @c RUUVI_DRIVER_ERROR_INVALID_STATE if trying to initialize sensor which
* already has been initialized.
**/
typedef ruuvi_driver_status_t (*ruuvi_driver_sensor_init_fp)(ruuvi_driver_sensor_t* const
p_sensor, const ruuvi_driver_bus_t bus, const uint8_t handle);Die Implementierung ist unkompliziert. Wir überprüfen die Eingaben, lesen die ID von TMP117 über I2C und konfigurieren dann die Funktionszeiger, den Sensornamen, die bereitgestellten Daten und gehen in den Ruhezustand. Die gespeicherte I2C-Adresse fungiert als Sperre für den Sensor.
ruuvi_driver_status_t ruuvi_interface_tmp117_init(ruuvi_driver_sensor_t*
environmental_sensor, ruuvi_driver_bus_t bus, uint8_t handle)
{
if(NULL == environmental_sensor) { return RUUVI_DRIVER_ERROR_NULL; }
if(m_address) { return RUUVI_DRIVER_ERROR_INVALID_STATE; }
ruuvi_driver_sensor_initialize(environmental_sensor);
ruuvi_driver_status_t err_code = RUUVI_DRIVER_SUCCESS;
m_address = handle;
size_t retries = 0;
switch(bus)
{
case RUUVI_DRIVER_BUS_I2C:
do{
err_code |= tmp117_validate_id();
retries++;
}while(RUUVI_DRIVER_ERROR_TIMEOUT == err_code && retries < 5);
break;
default:
return RUUVI_DRIVER_ERROR_INVALID_PARAM;
}
if(RUUVI_DRIVER_SUCCESS != err_code) { err_code = RUUVI_DRIVER_ERROR_NOT_FOUND; }
if(RUUVI_DRIVER_SUCCESS == err_code)
{
environmental_sensor->init = ruuvi_interface_tmp117_init;
environmental_sensor->uninit = ruuvi_interface_tmp117_uninit;
environmental_sensor->samplerate_set = ruuvi_interface_tmp117_samplerate_set;
environmental_sensor->samplerate_get = ruuvi_interface_tmp117_samplerate_get;
environmental_sensor->resolution_set = ruuvi_interface_tmp117_resolution_set;
environmental_sensor->resolution_get = ruuvi_interface_tmp117_resolution_get;
environmental_sensor->scale_set = ruuvi_interface_tmp117_scale_set;
environmental_sensor->scale_get = ruuvi_interface_tmp117_scale_get;
environmental_sensor->dsp_set = ruuvi_interface_tmp117_dsp_set;
environmental_sensor->dsp_get = ruuvi_interface_tmp117_dsp_get;
environmental_sensor->mode_set = ruuvi_interface_tmp117_mode_set;
environmental_sensor->mode_get = ruuvi_interface_tmp117_mode_get;
environmental_sensor->data_get = ruuvi_interface_tmp117_data_get;
environmental_sensor->configuration_set = ruuvi_driver_sensor_configuration_set;
environmental_sensor->configuration_get = ruuvi_driver_sensor_configuration_get;
environmental_sensor->name = m_sensor_name;
environmental_sensor->provides.datas.temperature_c = 1;
m_timestamp = RUUVI_DRIVER_UINT64_INVALID;
m_temperature = NAN;
tmp117_sleep();
}
return err_code;
}
ruuvi_driver_status_t ruuvi_interface_tmp117_uninit(ruuvi_driver_sensor_t* sensor,
ruuvi_driver_bus_t bus, uint8_t handle)
{
if(NULL == sensor) { return RUUVI_DRIVER_ERROR_NULL; }
ruuvi_driver_status_t err_code = RUUVI_DRIVER_SUCCESS;
tmp117_sleep();
ruuvi_driver_sensor_uninitialize(sensor);
m_timestamp = RUUVI_DRIVER_UINT64_INVALID;
m_temperature = NAN;
m_address = 0;
return err_code;
}Konfiguration
TMP117 hat eine feste Auflösung und Skalierung. Die Abtastrate kann über die Konvertierungszykluszeit im kontinuierlichen Modus konfiguriert werden. Die einzige digitale Signalverarbeitung (DSP), die von TMP117 unterstützt wird, ist Oversampling.
Wir geben eine Warnung zurück, wenn der Benutzer versucht, das Oversampling auf ein höheres Verhältnis einzustellen, als von der aktuellen Abtastrate unterstützt wird, oder umgekehrt. Zusätzlich unterstützt das Standard-Sensorkonfigurationsformat nur Abtastraten im Bereich von 1 … 200 Hz, während TMP117 Raten von 1/4, 1/8 und 1/16 Hz unterstützt. Wir implementieren diese Raten als benutzerdefinierte Werte für den Sensor.
Die Konfigurationsfunktionssignaturen sind in ruuvi_driver_sensor.h definiert. Lass uns sie mit den obigen Low-Level-Funktionen implementieren.
/**
* @brief Setup a parameter of a sensor.
* The function will modify the pointed data to the actual value which was written
*
* @param[in,out] parameter value to write to sensor configuration. Actual value written to sensor as output
* @return RUUVI_DRIVER_SUCCESS on success
* @return RUUVI_DRIVER_ERROR_NULL if parameter is NULL
* @return RUUVI_DRIVER_ERROR_NOT_SUPPORTED if sensor cannot support given parameter
* @return RUUVI_DRIVER_ERROR_NOT_IMPLEMENTED if the sensor could support parameter, but it's not implemented in fw.
**/
typedef ruuvi_driver_status_t (*ruuvi_driver_sensor_setup_fp)(uint8_t* parameter);
/**
* @brief Configure sensor digital signal processing.
* Takes DSP function and a DSP parameter as input, configured value or error code as output.
* Modifies input parameters to actual values written on the sensor.
* DSP functions are run on the sensor HW, not in the platform FW.
*
* @param[in,out] dsp_function. DSP function to run on sensor. Can be a combination of several functions.
* @param[in,out] dsp_parameter. Parameter to DSP function(s)
* @return RUUVI_DRIVER_SUCCESS on success
* @return RUUVI_DRIVER_ERROR_NULL if either parameter is NULL
* @return RUUVI_DRIVER_ERROR_NOT_SUPPORTED if sensor doesn't support given DSP
* @return RUUVI_DRIVER_ERROR_NOT_IMPLEMENTED if sensor supports given DSP, but
* driver does not implement it
* @return RUUVI_DRIVER_ERROR_INVALID_PARAM if parameter is invalid for any reason.
**/
typedef ruuvi_driver_status_t (*ruuvi_driver_sensor_dsp_fp)(uint8_t* dsp_function,
uint8_t* dsp_parameter);Signatur für Konfigurationsfunktion
Abtastrate
Die vom Benutzer angegebene Abtastrate wird als mindestens so viel verstanden. Daher runden wir die vom Benutzer angeforderte Abtastrate auf. Wir betrachten auch 1 Hz als Standardoption.
ruuvi_driver_status_t ruuvi_interface_tmp117_samplerate_set(uint8_t* samplerate)
{
ruuvi_driver_status_t err_code = RUUVI_DRIVER_SUCCESS;
if(RUUVI_DRIVER_SENSOR_CFG_DEFAULT == *samplerate ||
1 >= *samplerate)
{
*samplerate = 1;
err_code |= tmp117_samplerate_set(TMP117_VALUE_CC_1000_MS);
}
else if(2 >= *samplerate)
{
*samplerate = 2;
err_code |= tmp117_samplerate_set(TMP117_VALUE_CC_500_MS);
}
else if(4 >= *samplerate)
{
*samplerate = 4;
err_code |= tmp117_samplerate_set(TMP117_VALUE_CC_250_MS);
}
else if(8 >= *samplerate)
{
*samplerate = 8;
err_code |= tmp117_samplerate_set(TMP117_VALUE_CC_125_MS);
}
else if(64 >= *samplerate)
{
*samplerate = 64;
err_code |= tmp117_samplerate_set(TMP117_VALUE_CC_16_MS);
}
else if (RUUVI_DRIVER_SENSOR_CFG_CUSTOM_1 == *samplerate)
{
err_code |= tmp117_samplerate_set(TMP117_VALUE_CC_4000_MS);
}
else if (RUUVI_DRIVER_SENSOR_CFG_CUSTOM_2 == *samplerate)
{
err_code |= tmp117_samplerate_set(TMP117_VALUE_CC_8000_MS);
}
else if (RUUVI_DRIVER_SENSOR_CFG_CUSTOM_3 == *samplerate)
{
err_code |= tmp117_samplerate_set(TMP117_VALUE_CC_16000_MS);
}
return err_code;
}
ruuvi_driver_status_t ruuvi_interface_tmp117_samplerate_get(uint8_t* samplerate)
{
ruuvi_driver_status_t err_code = RUUVI_DRIVER_SUCCESS;
uint16_t reg_val;
err_code = ruuvi_interface_i2c_tmp117_read(m_address, TMP117_REG_CONFIGURATION, ®_val);
reg_val &= TMP117_MASK_CC;
switch(reg_val)
{
case TMP117_VALUE_CC_16_MS:
*samplerate = 64;
break;
case TMP117_VALUE_CC_125_MS:
*samplerate = 8;
break;
case TMP117_VALUE_CC_250_MS:
*samplerate = 4;
break;
case TMP117_VALUE_CC_500_MS:
*samplerate = 2;
break;
case TMP117_VALUE_CC_1000_MS:
*samplerate = 1;
break;
case TMP117_VALUE_CC_4000_MS:
*samplerate = RUUVI_DRIVER_SENSOR_CFG_CUSTOM_1;
break;
case TMP117_VALUE_CC_8000_MS:
*samplerate = RUUVI_DRIVER_SENSOR_CFG_CUSTOM_2;
break;
case TMP117_VALUE_CC_16000_MS:
*samplerate = RUUVI_DRIVER_SENSOR_CFG_CUSTOM_3;
break;
default:
return RUUVI_DRIVER_ERROR_INTERNAL;
}
return err_code;
}Das Konfigurieren der Abtastrate endet als einfache, aber lange Switch-Case-Anweisung
Auflösung, Skalierung
Da Auflösung und Skalierung bei TMP117 fest vorgegeben sind, müssen wir nur Werte für Minimum, Maximum, Standard und „keine Änderung“ zulassen. Alles andere kann den Fehler „nicht unterstützt“ zurückgeben.
ruuvi_driver_status_t ruuvi_interface_tmp117_resolution_set(uint8_t* resolution)
{
if(NULL == resolution) { return RUUVI_DRIVER_ERROR_NULL; }
uint8_t original = *resolution;
*resolution = RUUVI_DRIVER_SENSOR_CFG_DEFAULT;
RETURN_SUCCESS_ON_VALID(original);
return RUUVI_DRIVER_ERROR_NOT_SUPPORTED;
}
ruuvi_driver_status_t ruuvi_interface_tmp117_resolution_get(uint8_t* resolution)
{
if(NULL == resolution) { return RUUVI_DRIVER_ERROR_NULL; }
*resolution = RUUVI_DRIVER_SENSOR_CFG_DEFAULT;
return RUUVI_DRIVER_SUCCESS;
}
ruuvi_driver_status_t ruuvi_interface_tmp117_scale_set(uint8_t* scale)
{
if(NULL == scale) { return RUUVI_DRIVER_ERROR_NULL; }
uint8_t original = *scale;
*scale = RUUVI_DRIVER_SENSOR_CFG_DEFAULT;
RETURN_SUCCESS_ON_VALID(original);
return RUUVI_DRIVER_ERROR_NOT_SUPPORTED;
}
ruuvi_driver_status_t ruuvi_interface_tmp117_scale_get(uint8_t* scale)
{
if(NULL == scale) { return RUUVI_DRIVER_ERROR_NULL; }
*scale = RUUVI_DRIVER_SENSOR_CFG_DEFAULT;
return RUUVI_DRIVER_SUCCESS;
}Einfach genug Boilerplate
Digitale Signalverarbeitung
TMP117 unterstützt Oversampling des Sensors, d. h. das Nehmen mehrerer Proben und deren Mittelung, um Rauschen zu reduzieren, aber den Stromverbrauch zu erhöhen. Die DSP-Konfigurationssignatur unterscheidet sich von den anderen Konfigurationsfunktionen, da sie sowohl den Typ von DSP als auch einen Parameter zur Konfiguration der Aggressivität von DSP enthält.
/**
* @brief Configure sensor digital signal processing.
* Takes DSP function and a DSP parameter as input, configured value or error code as output.
* Modifies input parameters to actual values written on the sensor.
* DSP functions are run on the sensor HW, not in the platform FW.
*
* @param[in,out] dsp_function. DSP function to run on sensor. Can be a combination of several functions.
* @param[in,out] dsp_parameter. Parameter to DSP function(s)
* @return RUUVI_DRIVER_SUCCESS on success
* @return RUUVI_DRIVER_ERROR_NULL if either parameter is NULL
* @return RUUVI_DRIVER_ERROR_NOT_SUPPORTED if sensor doesn't support given DSP
* @return RUUVI_DRIVER_ERROR_NOT_IMPLEMENTED if sensor supports given DSP, but
* driver does not implement it
* @return RUUVI_DRIVER_ERROR_INVALID_PARAM if parameter is invalid for any reason.
**/
typedef ruuvi_driver_status_t (*ruuvi_driver_sensor_dsp_fp)(uint8_t* dsp_function,
uint8_t* dsp_parameter);DSP-Konfigurationssignatur
Jetzt sind die erforderlichen Konfigurationsfunktionen vorhanden. Unsere Sensorschnittstelle würde das Setzen des Interrupts unterstützen, aber wir lassen die Implementierung vorerst weg.
Abtastung: Ruhezustand, Einzel, Kontinuierlich
Wie bei allen batteriebetriebenen Geräten ist Energie die wertvollste Ressource im System und muss so weit wie möglich geschont werden. Der niedrigste Stromzustand des Sensors ist der Ruhezustand. Eine Einzelmessung weckt den Sensor zum Nehmen einer Probe auf, wartet, bis die Probe abgeschlossen ist, und versetzt den Sensor dann wieder in den Ruhezustand. Der kontinuierliche Modus lässt den Sensor in einem frei laufenden Modus, der die Daten kontinuierlich abtastet.
Der kontinuierliche Modus ist nützlich, wenn Proben schnell genommen werden, wenn Interrupts basierend auf Sensorwerten verwendet werden oder wenn der Sensor interne digitale Signalverarbeitung ausführt.
Im Allgemeinen ist der Einzelmodus am besten, wenn Proben mit einer Frequenz unter 1 Hz genommen werden, aber da TMP117 kontinuierliche Abtastung bis zu 1/16 Hz unterstützt, können wir praktisch immer den kontinuierlichen Modus verwenden. Lass uns die High-Level-Aufrufe zum Einstellen des Modus implementieren.
ruuvi_driver_status_t ruuvi_interface_tmp117_mode_set(uint8_t* mode)
{
if(NULL == mode) { return RUUVI_DRIVER_ERROR_NULL; }
ruuvi_driver_status_t err_code = RUUVI_DRIVER_SUCCESS;
switch(*mode)
{
case RUUVI_DRIVER_SENSOR_CFG_CONTINUOUS:
err_code |= tmp117_continuous();
m_continuous = true;
break;
case RUUVI_DRIVER_SENSOR_CFG_SINGLE:
if(m_continuous) { return RUUVI_DRIVER_ERROR_INVALID_STATE; }
err_code |= tmp117_sample();
ruuvi_interface_delay_ms(ms_per_sample);
break;
case RUUVI_DRIVER_SENSOR_CFG_SLEEP:
err_code |= tmp117_sleep();
m_continuous = false;
break;
default:
err_code |= RUUVI_DRIVER_ERROR_INVALID_PARAM;
}
return err_code;
}
ruuvi_driver_status_t ruuvi_interface_tmp117_mode_get(uint8_t* mode)
{
if(NULL == mode) { return RUUVI_DRIVER_ERROR_NULL; }
*mode = m_continuous ? RUUVI_DRIVER_SENSOR_CFG_CONTINUOUS : RUUVI_DRIVER_SENSOR_CFG_SLEEP;
return RUUVI_DRIVER_SUCCESS;
}Setzen und Abrufen des aktuellen Modus
Daten auslesen
Als Nächstes kommen wir zum eigentlichen Kern unseres Treibers: dem Auslesen von Daten. Die Ruuvi-Sensordatenstruktur hat sich in FW-Version 3.26.0 geändert. Frühere Versionen zwangen jeden Sensor, seine Daten in 3 Floats einzupassen, während das neue Datenformat bis zu 32 Felder pro Sensor unterstützt. Wir lesen den Sensorwert aus und füllen das Temperaturdatenfeld.
/**
* @brief Read latest data from sensor registers
* Return latest data from sensor. Does not take a new sample, calling this function twice
* in a row returns same data. Configure sensor in a single-shot mode to take a new sample
* or leave sensor in a continuous mode to get updated data.
*
* @param [out] p_data Pointer to sensor data @ref ruuvi_driver_sensor_data_t .
* @return RUUVI_DRIVER_SUCCESS on success
* @return RUUVI_DRIVER_ERROR_NULL if p_data is @c NULL.
*
* @warning if sensor data is not valid for any reason, data is populated with
* @c RUUVI_DRIVER_FLOAT_INVALID.
*/
typedef ruuvi_driver_status_t (*ruuvi_driver_sensor_data_fp)(ruuvi_driver_sensor_data_t* const p_data);Sensordaten-Get-Signatur
ruuvi_driver_status_t ruuvi_interface_tmp117_mode_get(uint8_t* mode)
{
if(NULL == mode) { return RUUVI_DRIVER_ERROR_NULL; }
*mode = m_continuous ? RUUVI_DRIVER_SENSOR_CFG_CONTINUOUS : RUUVI_DRIVER_SENSOR_CFG_SLEEP;
return RUUVI_DRIVER_SUCCESS;
}
ruuvi_driver_status_t ruuvi_interface_tmp117_data_get(ruuvi_driver_sensor_data_t* const data)
{
if(NULL == data) { return RUUVI_DRIVER_ERROR_NULL; }
ruuvi_driver_status_t err_code = RUUVI_DRIVER_SUCCESS;
if(m_continuous)
{
m_temperature = tmp117_read();
m_timestamp = ruuvi_driver_sensor_timestamp_get();
}
if(RUUVI_DRIVER_SUCCESS == err_code && RUUVI_DRIVER_UINT64_INVALID != m_timestamp)
{
ruuvi_driver_sensor_data_fields_t env_fields = {.bitfield = 0};
env_fields.datas.temperature_c = 1;
ruuvi_driver_sensor_data_set(data,
env_fields,
m_temperature);
data->timestamp_ms = m_timestamp;
}
return err_code;
}Daten-Getter-Implementierung
Testen
Da dies ziemlich viel Code war, sind in der Arbeit, die wir implementiert haben, sicher Fehler enthalten. Deshalb haben wir die Unit-Tests für die Sensorschnittstelle. Wir führen dieselbe Testbatterie für jede Implementierung von Sensorschnittstellen durch, um sicherzustellen, dass ihr Verhalten konsistent ist. Auf diese Weise können wir verschiedene Sensoren auf verschiedenen Boards verwenden, ohne uns um die Details der zugrunde liegenden Implementierung sorgen zu müssen.
#if RUUVI_BOARD_ENVIRONMENTAL_TMP117_PRESENT
bus = RUUVI_DRIVER_BUS_I2C;
handle = RUUVI_BOARD_TMP117_I2C_ADDRESS;
err_code = test_run(ruuvi_interface_tmp117_init, bus, handle);
RUUVI_DRIVER_ERROR_CHECK(err_code, RUUVI_DRIVER_ERROR_SELFTEST);
#endifWir haben den Code zum Ausführen der Testbatterie zu tests/test_environmental.c hinzugefügt. Der Testabschnitt befindet sich in der oberen rechten Ecke des Anwendungsdiagramms.

Der Kürze halber gehen wir nur den ersten Fehler im Detail durch.


Der Fehler in Zeile test_sensor.c:433 hing ebenfalls mit dem Nehmen einer Einzelprobe zusammen, der Zeitstempel der Probe wurde nicht gesetzt.
Schließlich hing der Fehler in Zeile test_sensor.c:490 auch mit der Konfiguration des Sensors zum Nehmen einer Einzelprobe zusammen. Der Sensor muss das Nehmen einer Einzelprobe ablehnen, während er kontinuierlich ist, und den Modus nach dem Ablehnen des neuen Modus als kontinuierlich markieren. Der Code lehnte die Änderung nur ab, ohne den tatsächlichen Zustand auf den Modusparameter zu aktualisieren.
Die restlichen Fehler wurden nicht von der Unit-Test-Suite gekennzeichnet. Nach dem Hinzufügen einiger weiterer Überprüfungen zu den Tests wurden einige Fehler bei der Eingabeüberprüfung der Funktionen gefunden. Hauptsächlich betrafen die Fehler das Zulassen der Konfiguration des Sensors, während er sich nicht im Ruhezustand befand, und einige Fehler hingen mit der Konfiguration der Abtastrate zusammen.

Sensor zur Anwendung hinzufügen
Umgebungsaufgabe
Jetzt, da wir den Sensor einsatzbereit haben, müssen wir ihn zur Anwendung hinzufügen. Der logische Ort für den Sensor ist in task_environmental.c. Die Aufgaben befinden sich in der oberen linken Ecke des Anwendungsdiagramms.
Wir fügen den Sensor zur Aufzählung möglicher Umgebungssensoren hinzu, sodass der Platz für den Sensor nur auf Boards reserviert wird, die tatsächlich den TMP117 haben könnten.
Die Initialisierungsfunktion überprüft, ob eine Konfiguration für den Sensor im Flash gespeichert ist, und wenn nicht, verwendet sie die in der Anwendungskonfiguration definierten Standardwerte.
// Do not compile space for unused sensor drivers.
// Define enum in order of default preference of sensor being used.
// Default sensor can be overridden by calling a backend_set function.
enum{
#if APPLICATION_ENVIRONMENTAL_TMP117_ENABLED
ENV_TMP117_INDEX,
#endif
#if APPLICATION_ENVIRONMENTAL_SHTCX_ENABLED
ENV_SHTCX_INDEX,
#endif
#if APPLICATION_ENVIRONMENTAL_BME280_ENABLED
ENV_BME280_INDEX,
#endif
#if APPLICATION_ENVIRONMENTAL_NTC_ENABLED
ENV_LIS2DH12_INDEX,
#endif
#if APPLICATION_ENVIRONMENTAL_MCU_ENABLED
ENV_MCU_INDEX,
#endif
#if APPLICATION_ENVIRONMENTAL_LIS2DH12_ENABLED
ENV_LIS2DH12_INDEX,
#endif
ENV_SENSOR_COUNT
};
/** @brief Try to initialize TMP117 as environmental sensor
*
* Looks up appropriate pin definitions from ruuvi_boards.h
* Tries to load driver configuration from flash. If flash configuration is not available,
* uses application defaults from application_config.h.
*
* @return RUUVI_DRIVER_SUCCESS if TMP117 environmental is not enabled at compile time or if sensor is initialized.
* @return RUUVI_DRIVER_ERROR_NOT_FOUND if TMP117 environmental does not reply on bus but it's expected to be available
* @return RUUVI_DRIVER_ERROR_INVALID_STATE if some other user has already initialized the driver.
*/
static ruuvi_driver_status_t initialize_tmp117(void)
{
#if APPLICATION_ENVIRONMENTAL_TMP117_ENABLED
// Assume "Not found", gets set to "Success" if a usable sensor is present
ruuvi_driver_status_t err_code = RUUVI_DRIVER_ERROR_NOT_FOUND;
ruuvi_driver_bus_t bus = RUUVI_DRIVER_BUS_I2C;
uint8_t handle = RUUVI_BOARD_TMP117_I2C_ADDRESS;
// Initialize sensor.
err_code = ruuvi_interface_tmp117_init(&(m_environmental_sensors[ENV_TMP117_INDEX]),
bus, handle);
// return if failed.
if(RUUVI_DRIVER_SUCCESS != err_code) { return err_code; }
// Wait for flash operation to finish
while(task_flash_busy());
ruuvi_driver_sensor_configuration_t config;
err_code = task_flash_load(APPLICATION_FLASH_ENVIRONMENTAL_FILE,
APPLICATION_FLASH_ENVIRONMENTAL_TMP117_RECORD,
&config,
sizeof(config));
// If there is no stored configuration, use defaults.
if(RUUVI_DRIVER_SUCCESS != err_code)
{
LOG("LIS2DH12 temp config not found on flash, using defaults\r\n");
config.dsp_function = APPLICATION_ENVIRONMENTAL_TMP117_DSP_FUNC;
config.dsp_parameter = APPLICATION_ENVIRONMENTAL_TMP117_DSP_PARAM;
config.mode = APPLICATION_ENVIRONMENTAL_TMP117_MODE;
config.resolution = APPLICATION_ENVIRONMENTAL_TMP117_RESOLUTION;
config.samplerate = APPLICATION_ENVIRONMENTAL_TMP117_SAMPLERATE;
config.scale = APPLICATION_ENVIRONMENTAL_TMP117_SCALE;
// Store defaults to flash
err_code = task_flash_store(APPLICATION_FLASH_ENVIRONMENTAL_FILE,
APPLICATION_FLASH_ENVIRONMENTAL_TMP117_RECORD,
&config,
sizeof(config));
}
// Check flash operation status, allow not supported in case we're on 811
RUUVI_DRIVER_ERROR_CHECK(err_code, RUUVI_DRIVER_ERROR_NOT_SUPPORTED);
// Wait for flash operation to finish
while(task_flash_busy());
// Configure sensor
return task_sensor_configure(&(m_environmental_sensors[ENV_TMP117_INDEX]), &config, "");
#else
return RUUVI_DRIVER_SUCCESS;
#endif
}Die Sensoren werden nach der Reihenfolge in der obigen Aufzählung priorisiert. TMP117 wird zuerst versucht. Wenn der Sensor nach der Initialisierung keine Daten liefern kann, wird SHTC versucht, dann BME280 usw. Es ist auch möglich, zur Laufzeit ein neues Sensor-Backend auszuwählen.
Konfiguration
Wir müssen die Standardkonfiguration hinzufügen und das Kompilieren des Sensors für unsere Anwendung aktivieren. Für die Benutzerkonfiguration fügen wir hinzu
#define APPLICATION_ENVIRONMENTAL_TMP117_ENABLED (1 && RUUVI_BOARD_ENVIRONMENTAL_TMP117_PRESENT)zu application_config.h
Damit das ruuvi.drivers.c-Projekt nur die notwendigen Komponenten kompiliert, fügen wir hinzu
#define RUUVI_INTERFACE_ENVIRONMENTAL_TMP117_ENABLED APPLICATION_ENVIRONMENTAL_TMP117_ENABLEDzu application_driver_configuration.h. Die Konfigurationsdateien befinden sich in der oberen rechten Ecke des Anwendungsdiagramms.
Schließlich definieren wir die Standardkonfiguration in application_mode_default.h.
#ifndef APPLICATION_ENVIRONMENTAL_TMP117_DSP_FUNC
#define APPLICATION_ENVIRONMENTAL_TMP117_DSP_FUNC APPLICATION_ENVIRONMENTAL_DSPFUNC
#endif
#ifndef APPLICATION_ENVIRONMENTAL_TMP117_DSP_PARAM
#define APPLICATION_ENVIRONMENTAL_TMP117_DSP_PARAM APPLICATION_ENVIRONMENTAL_DSPPARAM
#endif
#ifndef APPLICATION_ENVIRONMENTAL_TMP117_MODE
#define APPLICATION_ENVIRONMENTAL_TMP117_MODE APPLICATION_ENVIRONMENTAL_MODE
#endif
#ifndef APPLICATION_ENVIRONMENTAL_TMP117_RESOLUTION
#define APPLICATION_ENVIRONMENTAL_TMP117_RESOLUTION APPLICATION_ENVIRONMENTAL_RESOLUTION
#endif
#ifndef APPLICATION_ENVIRONMENTAL_TMP117_SAMPLERATE
#define APPLICATION_ENVIRONMENTAL_TMP117_SAMPLERATE RUUVI_DRIVER_SENSOR_CFG_DEFAULT
#endif
#ifndef APPLICATION_ENVIRONMENTAL_TMP117_SCALE
#define APPLICATION_ENVIRONMENTAL_TMP117_SCALE APPLICATION_ENVIRONMENTAL_SCALE
#endifDiese Definitionen können in verschiedenen Anwendungsmoduskonfigurationsdateien überschrieben werden.
Sensoren vergleichen
Hier zweigen wir von der Master-Version der Ruuvi-Firmware ab. Die Master-Firmware hat einen Heartbeat, der die Sensordaten an Listener aktualisiert. Standardmäßig ist dieser Heartbeat BLE-Werbung, aber während einer GATT-Verbindung werden die Daten stattdessen über den Nordic UART Service gesendet. Wir erstellen spezielle Logik, um den Sensor zu wechseln und die MAC-Adresse während der Übertragung zu ändern, um Daten von verschiedenen Sensoren zu erhalten.
static void select_next_backend()
{
static uint8_t index = 0;
static const char list[5][9] = { "BME280",
"LIS2DH12",
"SHTCX",
"nRF5TMP",
"TMP117"};
static uint8_t ids[] = {0x80, 0x12, 0xC3, 0x52, 0x17};
uint8_t id;
ruuvi_driver_status_t err_code = RUUVI_DRIVER_ERROR_NOT_FOUND;
while(err_code == RUUVI_DRIVER_ERROR_NOT_FOUND)
{
err_code = task_environmental_backend_set(list[index]);
id = ids[index++];
index = index % sizeof(ids);
}
uint64_t address;
uint64_t mask = 0xFFFFFFFFFFFFFF00;
err_code |= ruuvi_interface_communication_radio_address_get(&address);
address &= mask;
address |= id;
err_code |= task_advertisement_stop();
err_code |= ruuvi_interface_communication_radio_address_set(address);
RUUVI_DRIVER_ERROR_CHECK(err_code, ~RUUVI_DRIVER_ERROR_FATAL);
}
static void heartbeat_send(void* p_event_data, uint16_t event_size)
{
select_next_backend();
ruuvi_interface_communication_message_t msg = {0};
task_sensor_encode_to_5((uint8_t*)&msg.data);
msg.data_length = m_heartbeat_data_max_len;
task_advertisement_start();
ruuvi_driver_status_t err_code = RUUVI_DRIVER_ERROR_INTERNAL;
if(NULL != heartbeat_target)
{
err_code = heartbeat_target(&msg);
}
if(RUUVI_DRIVER_SUCCESS == err_code) { ruuvi_interface_watchdog_feed(); }
RUUVI_DRIVER_ERROR_CHECK(err_code, ~RUUVI_DRIVER_ERROR_FATAL);
}Sensoren werden durchlaufen, um vergleichbare Daten zu erhalten

Letzte Feinheiten
Das Gute zurück zum Master mergen
Während der Entwicklung der Sensorvergleichs-FW habe ich einige Fehler in der Firmware gefunden. Wenn der Beschleunigungssensor nicht initialisiert werden kann, steigt ein Fehler aus der Sensordatencodierung auf und die Anwendung betrachtet dies als fatales Problem und startet neu. Wir möchten jedoch in der Lage sein, die Firmware ohne Beschleunigungssensor zu verwenden, also betrachten wir es einfach als Warnung und lassen die Anwendung fortfahren.
Die Sensordateninitialisierung für die Übertragung verwendete Dummy-Werte. Wir ändern die Sensordateninitialisierung so, dass sie dieselbe Logik wie die Anwendungsübertragung verwendet. Dies hilft, Probleme mit dem Messsequenzzähler usw. zu vermeiden.
Lass uns die Unterschiede mit git status überprüfen:

Wir committen die Dateien in vier Gruppen: task_advertisement ist eine Gruppe, task_environmental und configuration ist die zweite und task_acceleration ist die dritte. Wir möchten die task_communication-Änderungen zurücklassen, da sie nur für diesen Branch waren, aber wir committen sie in der vierten Gruppe für die zukünftige Verwendung.

Unsere Sensorvergleichs-FW wurde auf den Master rebased, sodass alle Unterschiede auf der aktuellen Version der Firmware aufbauen. Jetzt möchten wir die 3 Commits 086f0ce, 73738f9 und 98b07ca zurück zum Master-Branch bringen.
Wir führen aus
git checkout master
git cherry-pick 086f0ce^..98b07caum die Commits zu erhalten. Es stellt sich heraus, dass der letzte Commit nichts im Vergleich zum Master-Branch hinzuzufügen hat. Die Beschleunigungssensorinitialisierung war wahrscheinlich irgendwo auf dem Weg kaputt gegangen.
Jetzt sieht unser Master so aus:

Als Nächstes rebasen wir den Sensorvergleichs-Branch auf den Master, um ihn auf dem neuesten Stand zu halten.

Code formatieren und Doxygen-Seiten generieren
Damit der Stil im Projekt konsistent bleibt, verwenden wir Artistic Style, um den Code in ein einheitliches Format zu bringen. Es ist nicht ganz so, wie ich es gern hätte, weil Kommentare am Zeilenende zur Zeilenlänge zählen und mich das praktisch dazu zwingt, keine Kommentare in Codezeilen zu verwenden, um seltsame Einrückungen zu vermeiden – aber es ist definitiv besser als nichts. Wir gehen die Submodule separat durch und starten mit ruuvi.drivers.c.
astyle --project=.astylerc --recursive "./interfaces/*.c"
astyle --project=.astylerc --recursive "./interfaces/*.h"
astyle --project=.astylerc --recursive "./nrf5_sdk15_platform/*.c"
astyle --project=.astylerc --recursive "./nrf5_sdk15_platform/*.h"Als Nächstes prüfen wir mit Doxygen, ob die Kommentare vollständig und aktuell sind. Doxygen durchsucht den Quellcode nach Kommentaren und erzeugt HTML- und PDF-Dokumentation zu Funktionen, Strukturen, Enumerationen, Makros und so weiter. Die Doxygen-Generierung ist ganz einfach:
doxygenDie Generierung erzeugt die Datei doxygen.error mit 55 Einträgen. Wir bauen diese technische Schuld heute ein Stück weit ab, stellen sicher, dass keiner dieser Fehler aus TMP117-Dateien stammt, und beheben die erste Datei mit Fehlern – ruuvi_interface_shtcx.c.
Wir wiederholen diesen Prozess im Hauptprojektordner, auch wenn es dort noch keine Doxygen-Unterstützung gibt.
Arbeit teilen
Da wir noch in der Alpha sind und es überall deutliche Hinweise gibt, dass das Projekt in Entwicklung ist, pushen wir einfach direkt auf master und lassen Travis und Jenkins entscheiden, ob das Projekt bereit ist.
Travis baut die Doxygen-Dokumentation für ruuvi.drivers.c und pusht die fertigen Dokumente in den Branch gh-pages. Jenkins baut die Firmware-Varianten für die Boards und stellt die gebauten Binaries online für alle bereit.
In diesem Fall entdeckt Jenkins einen Fehler: Wir haben ruuvi_interface_tmp117.c nicht zu den GCC-Quellen in der Datei gcc_sources.make hinzugefügt. Nachdem wir die Datei ergänzt haben, können wir es erneut versuchen.
Compiling file: ruuvi_interface_tmp117.c
../../../ruuvi.drivers.c/interfaces/environmental/ruuvi_interface_tmp117.c: In function 'ruuvi_interface_tmp117_dsp_get':
../../../ruuvi.drivers.c/interfaces/environmental/ruuvi_interface_tmp117.c:508:25: error: variable 'err_code' set but not used [-Werror=unused-but-set-variable]
ruuvi_driver_status_t err_code;
^~~~~~~~
../../../ruuvi.drivers.c/interfaces/environmental/ruuvi_interface_tmp117.c:534:1: error: control reaches end of non-void function [-Werror=return-type]
}
^
At top level:
../../../ruuvi.drivers.c/interfaces/environmental/ruuvi_interface_tmp117.c:42:30: error: 'tmp117_soft_reset' defined but not used [-Werror=unused-function]
static ruuvi_driver_status_t tmp117_soft_reset(void)
^~~~~~~~~~~~~~~~~
cc1: all warnings being treated as errorsHier können wir noch ein paar weitere Fehler abfangen, die die statische Analyse von GCC findet. Eine unserer Funktionen hat vergessen, den Fehlercode tatsächlich zurückzugeben, und unsere Initialisierung hat den TMP117 nicht in einen definierten Zustand zurückversetzt.
Nach den Fixes ist die Dokumentation automatisch unter github.io verfügbar, und die Builds sind unter jenkins.ruuvi.com verfügbar.
Stromverbrauch
Dieses Mal arbeiten wir an einem experimentellen Board und werden nicht verifizieren, ob der finale Stromverbrauch in einem akzeptablen Bereich liegt. Bevor wir den Code jedoch an Kunden ausliefern, achten wir der Umwelt zuliebe darauf und prüfen, dass der Stromverbrauch nicht übermäßig ist.
Fazit
Nach einem langen Beitrag haben wir einen neuen Sensor zur Ruuvi Firmware hinzugefügt. Das Wichtigste in diesem Beitrag waren allerdings nicht die Details, wie man etwas umsetzt. Die wichtigsten Erkenntnisse sind:
- Achte auf deine Architektur. Beim Programmieren hatte der Treiber viel Komplexität, aber sobald der Treiber fertig war, brauchten wir weniger als 100 Zeilen Code, um den Sensor zu initialisieren, die Sensorkonfiguration aus dem Flash zu laden oder die Standardkonfiguration zu verwenden, wenn im Flash keine Konfiguration vorhanden ist, und die kodierten Daten je nach Programmzustand über Bluetooth-Advertisements oder GATT zu senden. Und das funktioniert über 3 verschiedene Boards, 2 verschiedene MCUs und kann mit einem fehlenden Sensor sauber umgehen – mit Fallback auf den nächsten Sensor. Der Code profitiert außerdem von allen zukünftigen Verbesserungen und Bugfixes in der gemeinsamen Logik.
- Automatische Tests und wiederverwendbare Interfaces können dir den Tag retten. Wir hatten den High-Level-Zugriff auf den Sensor und automatische Tests bereits definiert, um den Sensortreiber über das Interface zu verifizieren. Die automatischen Tests haben 7 Bugs in der Implementierung gefunden, und eine weitere statische Analyse durch GCC hat einige Randfälle entdeckt, etwa dass die Initialisierung den Sensor nicht wirklich in einen definierten Zustand zurücksetzt. Die automatische Kompilierung hat außerdem ein Problem erkannt, bei dem das Projekt nach dem Push nicht mehr kompilierbar war.
Das war’s für dieses Mal. Wenn du dich fragst, was es mit diesen mysteriösen Kaarle- und Keijo-Boards auf sich hat, dann trag dich unten auf der Startseite in die Ruuvi-Mailingliste ein und erfahre es als einer der Ersten 😉
RuuviTags eignen sich auch hervorragend als Black-Friday- und Weihnachtsgeschenke, und ihre Verkäufe unterstützen die Erstellung dieser Beiträge. Wenn dir die Inhalte gefallen, teile Ruuvi mit deiner Familie! Und jetzt: Measure Your World.
