Обработка аналоговых сигналов

Цифровая техника на то и цифровая, что имеет дело с цифровыми сигналами, т.е. с условными нулями и единицами. Однако окружающий нас мир скорее аналоговый, чем цифровой, и обрабатывать аналоговые сигналы время от времени приходится. Чуть выше мы уже приводили пример с аналоговым датчиком температуры — NTC-термистором.

Значения АЦП могут скакать из-за присутствия шума. Здесь нас не интересует природа шума — огрехи самого АЦП, плохая разводка или что-то еще. Интересно другое — каким образом можно уменьшить уровень шума, т.е. сделать показания стабильными, насколько это возможно, программным путем.

Самая простая и очевидная техника для уменьшения шума в сигнале — усредняющий фильтр (англ. mean filter). Данные с АЦП собираются в некоторый массив, далее рассчитывается сумма и делится на количество элементов массива.

result = Σ adci / N

При этом количество измерений N, как вы уже догадались, лучше сделать степенью двойки, так как деление на степень двойки — это просто смещение, которое выполняется за один такт. Реализовать такой фильтр очень просто:

#define BUFFER_SIZE         16
#define SHIFT               4

volatile uint32_t buffer[BUFFER_SIZE] = { 0 };

uint32_t get_value() {
    static uint32_t i, sum;
    sum = 0;
    for (i = 0; i < SAMPLES_NUMBER; i++)
        sum += buffer[i];
    return sum >> SHIFT;
}

Такой подход довольно популярен, но имеет недостатки: а) для получения результата необходимо несколько измерений; б) усредняющий фильтр плохо обрабатывает выбросы. Если одно из измерений выдаст аномально большое значение, то и среднее значительно увеличится. Выходом является медианный фильтр (англ. median filter), в котором массив упорядочивается от минимума к максимуму (или наоборот), и выбирается значение из середины массива. Вообще, конечно, тема фильтрации — это совершенно отдельная тема для разговора, которой посвящены целые книги. Приведенный пример нужен скорее для демонстрации другой сущности, которая полезна в реализации данного фильтра, — циклического буфера (англ. circular/cyclic/ring buffer). Суть его проста — данные записываются последовательно, но как только мы доходим до конца массива, индекс сбрасывается и запись начинается с начала, с нулевого элемента. Таким образом, буфер хранит последние BUFFER_SIZE измерений.

#define BUFFER_SIZE 16
extern uint32_t buffer[BUFFER_SIZE];
extern uint32_t buffer_index = 0;
// ...
void ADC1_Handle(void) {
    samples[(++buffer_index < BUFFER_SIZE) ? buffer_index : 0] = ADC1_GetConversation();
    // clear pending bit
}

Приведенная реализация крайне примитивна: нам не важно, где голова (последний записанный элемент), а где хвост (элемент, записанный BUFFER_SIZE измерений назад). Однако реализацию придется существенно усложнить, если такая структура будет использоваться в качестве буфера для приема/передачи данных по некоторому интерфейсу (SPI, UART и т.д.) Рассмотрим ее в следующей главе, а пока приведем еще один пример фильтра, на этот раз с обратной связью (англ. feedback).

Рекурсивный фильтр (англ. recursive filter, также называют exponential filter) использует предыдущее сглаженное значение для вычисления текущего сглаженного значения. Математически он описывается так:

f(xn) = α · xn + (1 — α) · f(x-1) {/$$}

Прямое решение требует использовать вещественный тип переменных float. И снова, выиграв в размере необходимой оперативной памяти, мы проиграли в скорости выполнения.

float get_value(uint32_t adc_value) {
    static float value = 0;
    value = alpha * adc_value + (1 - alpha) * value;
    return value;
}
0

Изменено: