Простой алгоритм обработки показаний АЦП функцией скользящего среднего

Если в программе для МК нужно использовать встроенный АЦП, то в 99% случаев вы сталкиваетесь с проблемой шумных показаний.

Встроенные в МК АЦП сильно (2-4 значащих бита из типичных 12) шумят из-за расположения на одном кристалле с цифровым ядром. Типичное решение — засыпать, проводить измерение и просыпаться, подходит только для относительно простых программ, где вы можете позволить себе длительный простой ядра.

Соответственно, показания нужно усреднять. Очевидные решения — завести кольцевой буфер, писать показания туда и каждый раз при выводе усреднять. Работает хорошо, только если размер буфера больше хотя бы 64 элементов. Для 12-битного АЦП вам нужно 128 байт памяти, что в мире МК бывает слишком расточительно. Плюс рассчет среднего на восьмибитном МК тоже может занимать слишком много времени, несмотря на отсутствие операций с плавающей точкой.

Пробовал я и добавлять цифровой фильтрации к показаниям, но результат был всегда один — если буфер меньше чем на 100 элементов, то показания будут неприятно скакать.

И однажды ко мне пришла идея: а зачем нам вообще хранить весь буфер, если мы постоянно вычисляем сумму и она является всем смыслом существования буфера? А с каждым новым значением мы изменяем всего один элемент!

Так почему бы нам просто не хранить сумму, добавляя к ней очередной элемент, а вычитая… например, среднее?!

#define ADC_VSYS_BUFFER_SIZE 100
 
uint32_t adc_vsys_buf = 0;  // "Симулятор" буфера, который хранит сумму
uint8_t adc_vsys_num = 1;   // "Позиция" в буфере
 
uint16_t adc_get_system_voltage(void) 
{
	// Функция adc_get_system_voltage_raw() должна вернуть текущее показание АЦП
	uint16_t current_vsys = adc_get_system_voltage_raw();
	uint16_t mean_vsys;
 
	adc_vsys_buf += current_vsys;   // Прибавляем к сумме текущее показание АЦП
	mean_vsys = adc_vsys_buf / adc_vsys_num;  // И вычисляем среднее
 
	if (adc_vsys_num < ADC_VSYS_BUFFER_SIZE)
	{
		// Переменная суммы еще не хранит желаемое число "элементов" —
		// увеличиваем "позицию"
		adc_vsys_num++;
	{
	else
	{
		// В переменной суммы уже достаточно "элементов" —
		// вычитаем среднее
		adc_vsys_buf -= mean_vsys;
	}
 
	return mean_vsys;
}

Итого: 5 байт глобально, еще 4 локально и ноль операций с плавающей точкой. И все это «симулируя» поведение буфера на 100 элементов.

А результат из реального мира — шикарное плавное изменение показаний АЦП без какого либо «дрожжания» значений. Пользуйтесь!

0.00 avg. rating (0% score) - 0 votes