Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
199 changes: 199 additions & 0 deletions src/fw/applib/pressure_service.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
/* SPDX-FileCopyrightText: 2026 Core Devices LLC */
/* SPDX-License-Identifier: Apache-2.0 */

#include "pressure_service.h"
#include "pressure_service_private.h"

#include "drivers/pressure.h"
#include "process_state/app_state/app_state.h"
#include "syscall/syscall_internal.h"
#include "system/logging.h"

#include <stddef.h>

#define STANDARD_SEA_LEVEL_PA 101325

// Timer intervals in ms for each ODR preset
static uint32_t prv_odr_to_interval_ms(PressureODR odr) {
switch (odr) {
case PRESSURE_ODR_1HZ: return 1000;
case PRESSURE_ODR_5HZ: return 200;
case PRESSURE_ODR_10HZ: return 100;
case PRESSURE_ODR_25HZ: return 40;
case PRESSURE_ODR_50HZ: return 20;
default: return 1000;
}
}

static PressureServiceState *prv_get_state(void) {
return app_state_get_pressure_state();
}

static void prv_compute_altitude(PressureServiceState *state, PressureData *data) {
int32_t ref = state->ref_pressure_pa;
if (ref <= 0) {
ref = STANDARD_SEA_LEVEL_PA;
}

if (state->use_full_formula) {
data->altitude_cm = pressure_get_altitude_full_cm(data->pressure_pa, ref);
} else {
data->altitude_cm = pressure_get_altitude_cm(data->pressure_pa, ref);
}
}

// Syscall wrapper for hardware access from the timer callback, which runs in
// unprivileged app context.
DEFINE_SYSCALL(bool, sys_pressure_read_and_compute, PressureData *data) {
PressureServiceState *state = prv_get_state();

if (!pressure_read(&data->pressure_pa, &data->temperature_centideg)) {
return false;
}

prv_compute_altitude(state, data);
return true;
}

static void prv_timer_callback(void *context) {
PressureServiceState *state = prv_get_state();
if (!state->handler) {
return;
}

PressureData data = { 0 };
if (!sys_pressure_read_and_compute(&data)) {
return;
}

state->handler(&data);
}

// All exported functions use DEFINE_SYSCALL to escalate privileges, since they
// are called from unprivileged app code via the SDK jump table but need to
// access hardware drivers (pressure_read, pressure_set_odr).

DEFINE_SYSCALL(void, pressure_service_subscribe, PressureDataHandler handler, PressureODR odr) {
PressureServiceState *state = prv_get_state();

// Clean up any existing subscription
if (state->poll_timer) {
app_timer_cancel(state->poll_timer);
state->poll_timer = NULL;
}

state->handler = handler;
state->odr = odr;

if (!handler) {
return;
}

// Configure hardware for the requested rate
pressure_set_odr(odr);

uint32_t interval_ms = prv_odr_to_interval_ms(odr);
state->poll_timer = app_timer_register_repeatable(interval_ms,
prv_timer_callback,
NULL,
true /* repeating */);
}

DEFINE_SYSCALL(void, pressure_service_unsubscribe, void) {
PressureServiceState *state = prv_get_state();

if (state->poll_timer) {
app_timer_cancel(state->poll_timer);
state->poll_timer = NULL;
}
state->handler = NULL;

// Drop hardware back to low-power mode
pressure_set_odr(PRESSURE_ODR_1HZ);
}

DEFINE_SYSCALL(bool, pressure_service_set_data_rate, PressureODR odr) {
PressureServiceState *state = prv_get_state();

if (!state->handler || !state->poll_timer) {
return false;
}

if (!pressure_set_odr(odr)) {
return false;
}

state->odr = odr;
uint32_t interval_ms = prv_odr_to_interval_ms(odr);
app_timer_reschedule(state->poll_timer, interval_ms);
return true;
}

DEFINE_SYSCALL(bool, pressure_service_set_reference, void) {
PressureServiceState *state = prv_get_state();

int32_t pressure_pa;
if (!pressure_read(&pressure_pa, NULL)) {
return false;
}

state->ref_pressure_pa = pressure_pa;
return true;
}

DEFINE_SYSCALL(void, pressure_service_set_reference_pressure, int32_t ref_pressure_pa) {
PressureServiceState *state = prv_get_state();
state->ref_pressure_pa = ref_pressure_pa;
}

DEFINE_SYSCALL(void, pressure_service_use_full_formula, bool enable) {
PressureServiceState *state = prv_get_state();
state->use_full_formula = enable;
}

DEFINE_SYSCALL(bool, pressure_service_peek, PressureData *data) {
if (!data) {
return false;
}

PressureServiceState *state = prv_get_state();

if (!pressure_read(&data->pressure_pa, &data->temperature_centideg)) {
return false;
}

prv_compute_altitude(state, data);
return true;
}

DEFINE_SYSCALL(bool, pressure_service_set_filter_mode, PressureFilterMode mode) {
return pressure_set_filter_mode(mode);
}

DEFINE_SYSCALL(int32_t, pressure_service_get_altitude_cm, int32_t pressure_pa,
int32_t ref_pressure_pa) {
PressureServiceState *state = prv_get_state();
if (state->use_full_formula) {
return pressure_get_altitude_full_cm(pressure_pa, ref_pressure_pa);
} else {
return pressure_get_altitude_cm(pressure_pa, ref_pressure_pa);
}
}

void pressure_service_state_init(PressureServiceState *state) {
*state = (PressureServiceState) {
.handler = NULL,
.poll_timer = NULL,
.ref_pressure_pa = 0,
.odr = PRESSURE_ODR_1HZ,
.use_full_formula = false,
};
}

void pressure_service_state_deinit(PressureServiceState *state) {
if (state->poll_timer) {
app_timer_cancel(state->poll_timer);
state->poll_timer = NULL;
}
state->handler = NULL;
}
104 changes: 104 additions & 0 deletions src/fw/applib/pressure_service.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
/* SPDX-FileCopyrightText: 2026 Core Devices LLC */
/* SPDX-License-Identifier: Apache-2.0 */

#pragma once

#include "drivers/pressure.h"

#include <stdbool.h>
#include <stdint.h>

//! @addtogroup Foundation
//! @{
//! @addtogroup EventService
//! @{
//! @addtogroup PressureService
//!
//! \brief Using the Pebble barometric pressure sensor
//!
//! The PressureService provides access to the barometric pressure sensor for
//! altitude tracking. Apps can subscribe to periodic pressure/altitude readings,
//! configure the data rate, set a reference altitude, and choose between a fast
//! linear approximation or the full barometric formula for altitude calculation.
//!
//! Typical usage for a skydiving altimeter:
//! \code
//! // On the ground, before jump:
//! pressure_service_subscribe(my_handler, PRESSURE_ODR_1HZ);
//! pressure_service_set_reference(); // zero altitude here
//! pressure_service_use_full_formula(true);
//!
//! // When altitude > 30m, switch to high rate:
//! pressure_service_set_data_rate(PRESSURE_ODR_25HZ);
//!
//! // On landing, drop back:
//! pressure_service_set_data_rate(PRESSURE_ODR_1HZ);
//! \endcode
//! @{

//! Pressure/altitude data delivered to the app handler
typedef struct {
int32_t pressure_pa; //!< Absolute pressure in Pascals
int32_t temperature_centideg; //!< Temperature in 0.01 degrees C
int32_t altitude_cm; //!< Altitude in centimeters relative to reference
} PressureData;

//! Callback type for pressure data events
//! @param data Pointer to the latest pressure reading
typedef void (*PressureDataHandler)(PressureData *data);

//! Subscribe to the pressure data service. Once subscribed, the handler is
//! called at the rate specified by \p odr.
//! @param handler Callback to receive pressure data
//! @param odr The desired output data rate
void pressure_service_subscribe(PressureDataHandler handler, PressureODR odr);

//! Unsubscribe from the pressure data service.
void pressure_service_unsubscribe(void);

//! Change the data rate while subscribed.
//! This reconfigures the sensor hardware for the new rate.
//! @param odr The desired output data rate
//! @return true if the rate was changed successfully
bool pressure_service_set_data_rate(PressureODR odr);

//! Capture the current pressure as the reference (altitude = 0).
//! Subsequent altitude readings will be relative to this reference.
//! If no reference is set, altitude is computed relative to standard
//! sea-level pressure (101325 Pa).
//! @return true if the reference was captured successfully
bool pressure_service_set_reference(void);

//! Set the reference pressure to an explicit value.
//! @param ref_pressure_pa Reference pressure in Pascals
void pressure_service_set_reference_pressure(int32_t ref_pressure_pa);

//! Enable or disable the full barometric formula for altitude calculation.
//! When disabled, a faster linear approximation is used (accurate within ~1000m).
//! When enabled, a lookup-table-based hypsometric formula is used (accurate to ~7500m).
//! @param enable true to use the full formula, false for linear approximation
void pressure_service_use_full_formula(bool enable);

//! Read the current pressure data without a subscription (one-shot).
//! @param[out] data Pointer to a PressureData struct to fill
//! @return true if the read succeeded
bool pressure_service_peek(PressureData *data);

//! Calculate altitude relative to a given reference pressure.
//! Uses the full hypsometric formula or linear approximation depending on
//! the current formula setting (see \ref pressure_service_use_full_formula).
//! @param pressure_pa Current pressure in Pascals
//! @param ref_pressure_pa Reference pressure in Pascals (e.g. 101325 for MSL)
//! @return altitude in centimeters relative to the reference pressure
int32_t pressure_service_get_altitude_cm(int32_t pressure_pa, int32_t ref_pressure_pa);

//! Set the hardware IIR filter mode on the pressure sensor.
//! Use PRESSURE_FILTER_NONE for fastest response (e.g. skydiving altimeters).
//! Use PRESSURE_FILTER_SMOOTH for smoother readings (e.g. weather monitoring).
//! @param mode The desired filter mode
//! @return true if the mode was set successfully
bool pressure_service_set_filter_mode(PressureFilterMode mode);

//! @} // end addtogroup PressureService
//! @} // end addtogroup EventService
//! @} // end addtogroup Foundation
21 changes: 21 additions & 0 deletions src/fw/applib/pressure_service_private.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/* SPDX-FileCopyrightText: 2026 Core Devices LLC */
/* SPDX-License-Identifier: Apache-2.0 */

#pragma once

#include "pressure_service.h"
#include "app_timer.h"

typedef struct PressureServiceState {
PressureDataHandler handler;
AppTimer *poll_timer;
int32_t ref_pressure_pa; //!< Reference pressure for altitude (0 = use standard 101325 Pa)
PressureODR odr;
bool use_full_formula;
} PressureServiceState;

//! Initialize the pressure service state for a new app. Called during app_state_init.
void pressure_service_state_init(PressureServiceState *state);

//! Clean up any active subscriptions. Called during app teardown.
void pressure_service_state_deinit(PressureServiceState *state);
7 changes: 7 additions & 0 deletions src/fw/console/prompt_commands.h
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,10 @@ extern void command_accel_status(void);
extern void command_accel_selftest(void);
extern void command_accel_softreset(void);

extern void command_pressure_read(void);
extern void command_pressure_debug(void);
extern void command_pressure_reinit(void);

extern void command_dump_flash(const char*, const char*);
extern void command_crc_flash(const char*, const char*);
extern void command_format_flash(void);
Expand Down Expand Up @@ -297,6 +301,9 @@ extern void command_force_deepwfi(const char *arg);
static const Command s_prompt_commands[] = {
// PULSE entry point, needed for anything PULSE-related to work
{ "PULSEv1", pulse_start, 0 },
{ "baroreinit", command_pressure_reinit, 0 },
{ "barodbg", command_pressure_debug, 0 },
{ "baro", command_pressure_read, 0 },
#if KEEP_NON_ESSENTIAL_COMMANDS == 1
// ====================================================================================
// NOTE: The following commands are used by test automation.
Expand Down
8 changes: 8 additions & 0 deletions src/fw/drivers/i2c/definitions.h
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,14 @@ typedef struct I2CBusState {
int user_count;
#if MICRO_FAMILY_STM32F4
RtcTicks last_rail_stop_ticks;
#endif
#if MICRO_FAMILY_NRF5
// Per-bus write buffer for nRF52840 TWIM errata #219 workaround.
// DMA reads from this buffer during the transfer, so it must outlive the call
// to nrfx_twim_xfer(). Currently safe because transfers are synchronous.
// Will NOT work if transfers become async without additional lifetime guarantees.
#define I2C_WRITE_BUF_MAX 32
uint8_t write_buf[I2C_WRITE_BUF_MAX];
#endif
SemaphoreHandle_t event_semaphore;
PebbleMutex *bus_mutex;
Expand Down
Loading