Далеко не пятничный вопрос про алгоритм

Ну, это как сказать. Если все- таки делить, то лучше делить не сумму, а каждое значение уже при измерении. Тогда для буфера можно будет выбрать тип данных меньшей размерности.
А вообще, скользящих средних много. Взвешенное, экспоненциальное, модифицированное. И для некоторых хранить буфер из N значений вообще не нужно - новое значение среднего вычисляется из предыдущего среднего и одного последнего значения.
Это все наверно Дракула мог бы рассказать, если заглянет на огонек.

Ну не совсем так - надо ещё исключать случайные значения, которые не вписываются в установленную модель. Или, если позволяет концепция экперимента, создавать новую модель.

А вообще - тут есть 2 варианта:
1 - Мы отслеживаем процесс по известной модели. Тогда нам известны доступные отклонения в результатах и тогда отклонения - это или ошибки средств измерения или ошибки исходных факторов.
2 - Мы создаем модель на основе экспериментальных данных. Тут всё сложнее. Скорее всего “выбивающиеся” данные тоже надо хранить и в последующем обрабатывать т.к. никто не исключает интерференции, когда вроде ровненькая, в основном, функция развалится на 2, 3, 4… циклических функций и имеет непонятные экстримы.

Без разницы, постепенно сползет на новое значение и будет там усредняться.

То есть описанная мной «хотелка» называется «скользящее среднее». Благодарю, по изучаю самостоятельно. Если возникнут вопросы - напишу.

Только начал читать.) А я усредняю до 64.) Тогда сумма 10-ти битных значений помещается в int.) А потом “взял и поделил”).

не зная входных данных, их шумовую составляющую, сложно что-то рекомендовать, но если данные скачут в тех пределах что озвучено, скользящее среднее видимо не самый лучший алгоритм

А Вы то тут каким боком? :slight_smile:

Да, какие тут нахрен специфики, всё тривиально. У меня сделано шаблоном, чтобы как можно больше всякой фигни (типа размера буфера и типов) определялось на этапе компиляции - это сильно поможет оптимизатору. Кстати, здесь уже с Вашей суммой и со средним.

Файл RingBuffer.h
#ifndef  RINGBUFFER_H
#define  RINGBUFFER_H

//////////////////////////////////////////////
//
// Кольцевой буфер с поддеркой суммы элементов и среднего значения.
//
// Параметры шаблона:
//
//    bufSize - размер буфера
//    TBase - тип элемента буфера (по умолчанию uint16_t)
//    TSum - тип переменной для суммы элементов (по умолчанию тот же, что тип элемента буфера)
//
// TSum getSum(void) - возвращает 0, если буффер недозаполнен или текущую сумму
// TBase getAverage(void) - возвращает 0, если буффер недозаполнен или среднее элементов буфера
// TSum add(const TBase newElement) - добавляет новый элемент и возвращает текущую сумму
//
template <const uint8_t bufSize, typename TBase = uint16_t, typename TSum = TBase>
class RingBuffer {
protected:
   TBase buffer[bufSize];  // Буфер для последних bufSize элементов
   TSum m_sum; // Текущая сумма
   uint8_t m_index;  // Индекс в массиве buffer, куда будем писать следующий элемент
   uint8_t m_total;  // Количество элементов записанных в массив buffer (после bufSize не растёт)
   
public:
   //
   // Конструктор и функции очистки, возврата суммы и возврата среднего значения
   // достаточно тривиальны
   //
   inline RingBuffer(void) : m_sum(0), m_index(0), m_total(0) {}
   inline void clear(void) { return m_sum = m_index = m_total = 0; }
   inline TSum getSum(void) { return m_total == bufSize ? m_sum : 0; }
   inline TBase getAverage(void) { return (getSum() + bufSize / 2) / bufSize; }
   //
   // Функция добавления нового элемента
   // Поддерживает актуальной сумму элементов, находящихся в буфере
   //
   inline TSum add(const TBase newElement) {
      //
      // Если buffer уже полон, то вычтем из суммы значение, которое 
      // собираемся стирать, иначе увеличим на 1 количество значений в буфере
      //
      if (m_total == bufSize) m_sum -= buffer[m_index]; else m_total++; 
      //
      // Дальше всё линейно:
      // 1. увеличим суммы на "новый элемент"
      // 2. запишем новый элемент в буфер
      // 3. продвинем индекс на одну позицию вперёд
      // 4. и вернёт накопленную сумму
      //
      m_sum += newElement;
      buffer[m_index] = newElement;
      m_index =(m_index + 1) % bufSize;
      return getSum();
   }
};

#endif   // RINGBUFFER_H

Ну, и пример:

Файл примера
#include "RingBuffer.h"

template <typename T> inline Print & operator << (Print & s, T  n) { s.print(n); return s; }

void setup(void) {
	Serial.begin(9600);
	Serial.println("Fun begins!");

	RingBuffer<8> buffer;
	for (uint8_t i = 0; i < 32; i++) {
		buffer.add(i);
		Serial << "i=" << i << "; sum=" << buffer.getSum() << "; ave=" << buffer.getAverage() << "\r\n";
	}
}

void loop(void) {}
Результат
Fun begins!
i=0; sum=0; ave=0
i=1; sum=0; ave=0
i=2; sum=0; ave=0
i=3; sum=0; ave=0
i=4; sum=0; ave=0
i=5; sum=0; ave=0
i=6; sum=0; ave=0
i=7; sum=28; ave=4
i=8; sum=36; ave=5
i=9; sum=44; ave=6
i=10; sum=52; ave=7
i=11; sum=60; ave=8
i=12; sum=68; ave=9
i=13; sum=76; ave=10
i=14; sum=84; ave=11
i=15; sum=92; ave=12
i=16; sum=100; ave=13
i=17; sum=108; ave=14
i=18; sum=116; ave=15
i=19; sum=124; ave=16
i=20; sum=132; ave=17
i=21; sum=140; ave=18
i=22; sum=148; ave=19
i=23; sum=156; ave=20
i=24; sum=164; ave=21
i=25; sum=172; ave=22
i=26; sum=180; ave=23
i=27; sum=188; ave=24
i=28; sum=196; ave=25
i=29; sum=204; ave=26
i=30; sum=212; ave=27
i=31; sum=220; ave=28
3 лайка

Я ж писал что то же самое для дозиметра делаю. Спасибо. Вернее сделано уже, по-другому, не проверял только.

Почему для первых значений сумма и среднее равны нулю? В смысле, для чего? Если данные уже идут, почему их сразу не выводить?

Не, ну я имел в виду тривиальную вещь. Вот смотрите: допустим этот Ваш сигнал – обратная связь и конечная задача стоит управлять неким процессом и “поддерживать значение среднего по 10-ти измерениям на уровне 800”. Но ведь эта задача полностью эквивалентна задаче “поддерживать сумму 10-ти измерений на уровне 8000”. Разве нет? Только во втором случае вычислений меньше.

Потому, что я так сделал. Хотите - выдавайте текущую (маленькую) сумму и среднее, деля на количество уже полученных данных. Но Вы при этом попадаете на настоящее деление, а если делить всегда на размер буфера, то можно обойтись сдвигами, если размер буфера – степень двойки.

1 лайк

Я почему-то тоже так сделал :slight_smile:

1 лайк

Да. Понятно, спасибо.
Пожалуй, тоже переделаю с 10 до 16 значений, только сначала забью буфер первым значением. Под мои условия подходит.

Я как-то делал, и 0 не подходил. Заполнил “примерно ожидаемыми значениями”. Хотя можно и просто отбросить, если есть возможность.

Евгений Петрович, сердечно благодарю! :slight_smile:

Я думаю Вы правы. Меня смущало вот что - Не возникнет ли «ненужного» округления при малых значениях (то есть - не пострадает ли точность при таком подходе)?

Ну вот для дозиметра тоже не подходит. Если по 10 сек замер, не ждать же 160 сек при массиве х16. А вообще, хочется сменить алгоритм на другой, более отзывчивый на амплитуду. Но это другая тема.

как это не печально, но в ссылке больше полезного чем тут написали

2 лайка

Ну, это забота компилятора, ты пишешь как красиво, а он уже обходится сдвигами. Но не каждый! Столкнулся с этим с ПИК семейством. Причём, у бесплатного код воооообще раздут. Типа, без оптимизации.)

это если делитель - константа, а в общем случае компилятор не может предположить на степень двойки ты делишь, или нет.

Ну да, естественно. Только зачем в данном случае эти извращения.)

и это правильное решение