Skip to content

Commit

Permalink
Enhanced sliding average...
Browse files Browse the repository at this point in the history
  • Loading branch information
FredM67 committed Jan 22, 2024
1 parent 1ea9c53 commit eef84ea
Show file tree
Hide file tree
Showing 29 changed files with 1,113 additions and 23 deletions.
147 changes: 126 additions & 21 deletions Mk2_3phase_RFdatalog_temp/movingAvg.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,85 +16,190 @@

#include "type_traits.hpp"

template< typename T, uint8_t N = 10 >
/**
* @brief Template class for implementing a sliding average
*
* @tparam T Type of values to be stored
* @tparam DURATION_IN_MINUTES Size of main array
* @tparam VALUES_PER_MINUTE Size of sub array
*
* @note
* Since the Arduino RAM is tiny, we need to store a few values as possible.
* Suppose, you want a sliding window of 10 minutes and receive a value every 5 seconds.
* With one single array, 10 x 12 values would be necessary to store.
* Using 2 arrays, one to store incoming values over 1 minute and one to store values every minutes,
* we need only 12 + 10 values.
* How it works :
* - the average is calculated over 1 minute with incoming values
* - this sub-average is added to the main array
* - the average is calculated on the main array
*
* Drawback of this method: the average is updated only every minutes !
*/
template< typename T, uint8_t DURATION_IN_MINUTES = 10, uint8_t VALUES_PER_MINUTE = 10 >
class movingAvg
{
public:
/**
* @brief Reset everything
*
*/
void clear()
{
_idx = 0;
_sub_idx = 0;

if constexpr (is_floating_point< T >::value)
_sum = 0.0;
{
_sum = 0.0F;
_sub_sum = 0.0F;
}
else
{
_sum = 0;
_sub_sum = 0;
}

uint8_t i{ N };
do
{
uint8_t i{ DURATION_IN_MINUTES };
do {
--i;
if constexpr (is_floating_point< T >::value)
_ar[i] = 0.0;
{
_ar[i] = 0.0F;
_sub_ar[i] = 0.0F;
}
else
{
_ar[i] = 0;
_sub_ar[i] = 0;
}
} while (i);
}

/**
* @brief Add a value
*
* @param _value Value to be added
*/
void addValue(const T& _value)
{
_sum -= _ar[_idx];
_ar[_idx] = _value;
_sum += _value;
++_idx;
_sub_sum -= _sub_ar[_sub_idx];
_sub_ar[_sub_idx] = _value;
_sub_sum += _value;
++_sub_idx;

if (_idx == N)
if (_sub_idx == VALUES_PER_MINUTE)
{
_idx = 0; // faster than %
_sub_idx = 0; // faster than %
_addValue(_getAverage());
}
}

void fillValue(const T& _value)
{
_idx = 0;
_sum = N * _value;
_sum = DURATION_IN_MINUTES * _value;

uint8_t i{ N };
uint8_t i{ DURATION_IN_MINUTES };
do
{
_ar[--i] = _value;
} while (i);
}

/**
* @brief Get the sliding average
*
* @return auto The sliding average
*
* @note This value is updated every minute, except for the special case of a duration of
* ONE minute. In this case, it is updated for each new input value.
*/
auto getAverage() const
{
return _sum / N;
if constexpr (DURATION_IN_MINUTES == 1)
return _getAverage();
else if constexpr (DURATION_IN_MINUTES == 2)
return _sum >> 1;
else if constexpr (DURATION_IN_MINUTES == 4)
return _sum >> 2;
else if constexpr (DURATION_IN_MINUTES == 8)
return _sum >> 3;
else if constexpr (DURATION_IN_MINUTES == 16)
return _sum >> 4;
else if constexpr (DURATION_IN_MINUTES == 32)
return _sum >> 5;
else
return _sum * invN;
}

auto getElement(uint8_t idx) const
{
if (idx >= N)
if (idx >= DURATION_IN_MINUTES)
{
if constexpr (is_floating_point< T >::value)
return 0.0;
return 0.0F;
else
return 0;
return (T)0;
}

return _ar[idx];
}

constexpr uint8_t getSize() const
{
return N;
return DURATION_IN_MINUTES;
}

private:
void _clear_sub()
{
_sub_idx = 0;

if constexpr (is_floating_point< T >::value)
_sub_sum = 0.0F;
else
_sub_sum = 0;

uint8_t i{ VALUES_PER_MINUTE };
do {
--i;
if constexpr (is_floating_point< T >::value)
_sub_ar[i] = 0.0F;
else
_sub_ar[i] = 0;
} while (i);
}

void _addValue(const T& _value)
{
_sum -= _ar[_idx];
_ar[_idx] = _value;
_sum += _value;
++_idx;

if (_idx == DURATION_IN_MINUTES)
{
_idx = 0; // faster than %
}
}

auto _getAverage() const
{
return _sub_sum * invD;
}

private:
uint8_t _idx{ 0 };
uint8_t _sub_idx{ 0 };
typename conditional< is_floating_point< T >::value, T, int32_t >::type _sum{ 0 };
typename conditional< is_floating_point< T >::value, T, int32_t >::type _sub_sum{ 0 };

T _ar[N]{};
T _sub_ar[VALUES_PER_MINUTE]{};
T _ar[DURATION_IN_MINUTES]{};

static constexpr float invN{ 1 / N };
static constexpr float invD{ 1.0 / VALUES_PER_MINUTE };
static constexpr float invN{ 1.0 / DURATION_IN_MINUTES };
};

#endif
2 changes: 1 addition & 1 deletion Mk2_3phase_RFdatalog_temp/utils_relay.h
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,7 @@ template< uint8_t T = 1 > class relayOutput
mutable uint16_t duration{ 0 }; /**< Duration of the current state */
mutable bool relayIsON{ false }; /**< True if the relay is ON */

static inline movingAvg< int16_t, T * 60 / DATALOG_PERIOD_IN_SECONDS > sliding_Average;
static inline movingAvg< int16_t, T, 60 / DATALOG_PERIOD_IN_SECONDS > sliding_Average;
};

#endif // _UTILS_RELAY_H
5 changes: 4 additions & 1 deletion Mk2_3phase_RFdatalog_temp/validation.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,10 @@ static_assert(!(DUAL_TARIFF & (ul_OFF_PEAK_DURATION > 12)), "******** Off-peak d

static_assert(!EMONESP_CONTROL || (DIVERSION_PIN_PRESENT && DIVERSION_PIN_PRESENT && (PRIORITY_ROTATION == RotationModes::PIN) && OVERRIDE_PIN_PRESENT), "******** Wrong configuration. Please check your config.h ! ********");

constexpr uint16_t check_pins()
static_assert(!RELAY_DIVERSION | (60 / DATALOG_PERIOD_IN_SECONDS * DATALOG_PERIOD_IN_SECONDS == 60), "******** Wrong configuration. DATALOG_PERIOD_IN_SECONDS must be a divider of 60 ! ********");

constexpr uint16_t
check_pins()
{
uint16_t used_pins{ 0 };

Expand Down
111 changes: 111 additions & 0 deletions dev/MathPerfTests/MathPerfTests.ino
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@

#include <Arduino.h>

#include "movingAvg.h"

const int nb_of_interation_per_pass = 30000;
const int nb_of_pass = 100;

unsigned long initial_time = 0;
unsigned long final_time = 0;

float duration_dummy_loop = 0;
float duration = 0;
float duration_sum = 0;


// All variables used for calculations are declared globally and volatile to minimize
// any possible compiler optimisation when performing the same operation multiple times.

volatile int dummy = 0;

volatile float float_1 = 0;
volatile float float_2 = 0;
volatile float float_3 = 0;

volatile long long_1 = 0;
volatile long long_2 = 0;
volatile long long_3 = 0;

volatile int int_1 = 0;
volatile int int_2 = 0;
volatile int int_3 = 0;

volatile byte byte_1 = 0;
volatile byte byte_2 = 0;
volatile byte byte_3 = 0;

volatile boolean bool_1 = 0;
volatile boolean bool_2 = 0;
volatile boolean bool_3 = 0;

movingAvg< int32_t, 1, 12 > sliding_Average;

void setup() {

Serial.begin(115200);
}

void loop() {

for (int j = 0; j < nb_of_pass; j++) {

// STEP 1: We first calculate the time taken to run a dummy FOR loop to measure the overhead cause by the execution of the loop.
initial_time = micros();
for (int i = 0; i < nb_of_interation_per_pass; i++) {
dummy++; // A dummy instruction is introduced here. If not, the compiler is smart enough to just skip the loop entirely...
}
final_time = micros();
duration_dummy_loop = float(final_time - initial_time) / nb_of_interation_per_pass; // The average duration of a dummy loop is calculated
dummy = 0;

// STEP 2 (optional): Pick some relevant random numbers to test the command under random conditions. Make sure to pick numbers appropriate for your command (e.g. no negative number for the command "sqrt()")
randomSeed(micros() * analogRead(0));
long_1 = random(-36000, 36000);

// STEP 3: Calculation of the time taken to run the dummy FOR loop and the command to test.
initial_time = micros();
for (int i = 0; i < nb_of_interation_per_pass; i++) {
dummy++; // The dummy instruction is also performed here so that we can remove the effect of the dummy FOR loop accurately.
// **************** PUT YOUR COMMAND TO TEST HERE ********************
sliding_Average.addValue(i);

long_3 = sliding_Average.getAverage();
// **************** PUT YOUR COMMAND TO TEST HERE ********************
}
final_time = micros();

// STEP 4: Calculation of the time taken to run only the target command.
duration = float(final_time - initial_time) / nb_of_interation_per_pass - duration_dummy_loop;
duration_sum += duration;
dummy = 0;

Serial.print(sliding_Average.getAverage());
Serial.print(" - ");
Serial.print(j);
Serial.print(". ");
print_result(duration);
}

Serial.println();
Serial.println("********* FINAL AVERAGED VALUE *********** ");
print_result(duration_sum / nb_of_pass);
Serial.println("****************************************** ");
Serial.println();
for (uint8_t idx = 0; idx < sliding_Average.getSize(); ++idx) {
Serial.println(sliding_Average.getElement(idx));
}
Serial.println();
duration_sum = 0;
delay(2000);
}

void print_result(float value_to_print) {
Serial.print("Time to execute command: ");
Serial.print("\t");
Serial.print(value_to_print, 3);
Serial.print(" us");
Serial.print("\t");
Serial.print(round(value_to_print * 16));
Serial.println(" cycles");
}
Loading

0 comments on commit eef84ea

Please sign in to comment.