Skip to content
Merged
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
5 changes: 3 additions & 2 deletions src/components/ble/SimpleWeatherService.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
#include "components/datetime/DateTimeController.h"
#include <lvgl/lvgl.h>
#include "displayapp/InfiniTimeTheme.h"
#include "utility/Math.h"

int WeatherCallback(uint16_t connHandle, uint16_t attrHandle, struct ble_gatt_access_ctxt* ctxt, void* arg);

Expand Down Expand Up @@ -77,11 +78,11 @@ namespace Pinetime {
}

[[nodiscard]] int16_t Celsius() const {
return (PreciseCelsius() + 50) / 100;
return Utility::RoundedDiv(PreciseCelsius(), static_cast<int16_t>(100));
}

[[nodiscard]] int16_t Fahrenheit() const {
return (PreciseFahrenheit() + 50) / 100;
return Utility::RoundedDiv(PreciseFahrenheit(), static_cast<int16_t>(100));
}

[[nodiscard]] lv_color_t Color() const {
Expand Down
19 changes: 10 additions & 9 deletions src/displayapp/DisplayApp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,13 @@
#include "displayapp/screens/settings/SettingBluetooth.h"
#include "displayapp/screens/settings/SettingOTA.h"

#include "utility/Math.h"

#include "libs/lv_conf.h"
#include "UserApps.h"

#include <algorithm>
#include <limits>

using namespace Pinetime::Applications;
using namespace Pinetime::Applications::Display;
Expand Down Expand Up @@ -167,18 +170,15 @@ TickType_t DisplayApp::CalculateSleepTime() {
// Calculates how many system ticks DisplayApp should sleep before rendering the next AOD frame
// Next frame time is frame count * refresh period (ms) * tick rate

auto RoundedDiv = [](uint32_t a, uint32_t b) {
return ((a + (b / 2)) / b);
};
// RoundedDiv overflows when numerator + (denominator floordiv 2) > uint32 max
// in this case around 9 hours (=overflow frame count / always on refresh period)
constexpr TickType_t overflowFrameCount = (UINT32_MAX - (1000 / 16)) / ((configTICK_RATE_HZ / 8) * alwaysOnRefreshPeriod);
// Avoid overflow when numerator would be > uint32 max
// in this case around 18 hours (=overflow frame count / always on refresh period)
constexpr TickType_t overflowFrameCount = std::numeric_limits<TickType_t>::max() / ((configTICK_RATE_HZ / 8) * alwaysOnRefreshPeriod);

TickType_t ticksElapsed = xTaskGetTickCount() - alwaysOnStartTime;
// Divide both the numerator and denominator by 8 (=GCD(1000,1024))
// to increase the number of ticks (frames) before the overflow tick is reached
TickType_t targetRenderTick = RoundedDiv((configTICK_RATE_HZ / 8) * alwaysOnFrameCount * alwaysOnRefreshPeriod, 1000 / 8);

TickType_t targetRenderTick =
Utility::RoundedDiv((configTICK_RATE_HZ / 8) * alwaysOnFrameCount * alwaysOnRefreshPeriod, static_cast<uint32_t>(1000 / 8));
// Assumptions

// Tick rate is multiple of 8
Expand All @@ -188,7 +188,8 @@ TickType_t DisplayApp::CalculateSleepTime() {
// Frame count must always wraparound more often than the system tick count does
// Always on overflow time (ms) < system tick overflow time (ms)
// Using 64bit ints here to avoid overflow
static_assert((uint64_t) overflowFrameCount * (uint64_t) alwaysOnRefreshPeriod < (uint64_t) UINT32_MAX * 1000ULL / configTICK_RATE_HZ);
static_assert((uint64_t) overflowFrameCount * (uint64_t) alwaysOnRefreshPeriod <
(uint64_t) std::numeric_limits<TickType_t>::max() * 1000ULL / configTICK_RATE_HZ);

if (alwaysOnFrameCount == overflowFrameCount) {
alwaysOnFrameCount = 0;
Expand Down
14 changes: 5 additions & 9 deletions src/heartratetask/HeartRateTask.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,12 @@
#include <components/heartrate/HeartRateController.h>
#include <limits>

#include "utility/Math.h"

using namespace Pinetime::Applications;

namespace {
constexpr TickType_t backgroundMeasurementTimeLimit = 30 * configTICK_RATE_HZ;

// dividend + (divisor / 2) must be less than the max T value
template <std::unsigned_integral T>
constexpr T RoundedDiv(T dividend, T divisor) {
return (dividend + (divisor / static_cast<T>(2))) / divisor;
}
}

std::optional<TickType_t> HeartRateTask::BackgroundMeasurementInterval() const {
Expand Down Expand Up @@ -48,9 +44,9 @@ TickType_t HeartRateTask::CurrentTaskDelay() {
static_cast<uint64_t>((Pinetime::Controllers::Ppg::deltaTms)) <
std::numeric_limits<uint32_t>::max(),
"Overflow");
TickType_t elapsedTarget = RoundedDiv(static_cast<uint32_t>(configTICK_RATE_HZ / 2) * (static_cast<uint32_t>(count) + 1U) *
static_cast<uint32_t>((Pinetime::Controllers::Ppg::deltaTms)),
static_cast<uint32_t>(1000 / 2));
TickType_t elapsedTarget = Utility::RoundedDiv(static_cast<uint32_t>(configTICK_RATE_HZ / 2) * (static_cast<uint32_t>(count) + 1U) *
static_cast<uint32_t>((Pinetime::Controllers::Ppg::deltaTms)),
static_cast<uint32_t>(1000 / 2));

// On count overflow, reset both count and start time
// Count is 16bit to avoid overflow in elapsedTarget
Expand Down
23 changes: 23 additions & 0 deletions src/utility/Math.h
Original file line number Diff line number Diff line change
@@ -1,10 +1,33 @@
#pragma once

#include <cstdint>
#include <concepts>

namespace Pinetime {
namespace Utility {
// returns the arcsin of `arg`. asin(-32767) = -90, asin(32767) = 90
int16_t Asin(int16_t arg);

// Round half away from zero integer division
// If T signed, divisor cannot be std::numeric_limits<T>::min()
// Adapted from https://github.com/lucianpls/rounding_integer_division
// Under the MIT license
template <std::integral T>
constexpr T RoundedDiv(T dividend, T divisor) {
bool neg = divisor < 0;
if (neg) {
// overflows if divisor is minimum value for T
divisor = -divisor;
}

T m = dividend % divisor;
T h = divisor / 2 + divisor % 2;
T res = (dividend / divisor) + (!(dividend < 0) & (m >= h)) - ((dividend < 0) & ((m + h) <= 0));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I may be missing something, but m and h are purely positive or 0. so the m+h should only ve true if both are 0?

so maybe I dont understand the negative divident case 😅

Copy link
Member Author

@mark9064 mark9064 Dec 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

m can be negative. For example RoundedDiv(-23, 17) would make m = -23 % 17 = -6


if (neg) {
res = -res;
}
return res;
}
}
}