В этом тексте я бы хотел написать про своеобразный простенький фильтр нижних частот. Про гистерезисный фильтр на триггерах Шмитта (ТШ).

Постановка задачи

В микроконтроллерной системе аналого-цифровой преобразователь измеряет напряжение на канале. Это напряжение надо конвертировать в дискретный уровень, в зависимости от значения. Сигнал зашумлен. Надо реализовать программный гистерезис. Напишите функцию, которая выполнит эту работу.

float hysteresis(float input_percent);

На графике это выглядит так

Теория

Триггеры Шмитта можно рассматривать как однобитные ячейки памяти. Получается, что фильтр из четырех триггеров формально может пребывать в (2^4=16) шестнадцати состояниях. В предельных случаях каждый новый семпл переключает каждый триггер.

Когда поступает очередной семпл мы подаем его на вход каждому триггеру: 1-му, 2-му, 3-му, 4-му. Где-то произойдет переключение состояние, а где-то нет. Теоретически регистр фильтра может быть в 16ти состояниях.

Для простоты рассуждений положим, что гистерезис равен нулю. То есть триггеры Шмитта вырождаются в компараторы. Надо сразу отметить, что некоторые состояния фильтра просто невозможны. Например не может быть входной семпл одновременно меньше 12 и больше 87. Вот перед Вами все запретные состояния фильтра.

Фильтр будет работать только в пяти состояниях термокода. Термокод - это способ двоичного кодирования чисел при который просто увеличивается количество непрерывных в ряд единиц.

dec

термокод

hex

0

000000

0x0

1

100000

0x1

2

110000

0x3

3

111000

0x7

4

111100

0xF

5

111110

0x1F

Вот рабочий диапазон значений триггеров

В общем таблица состояний получается вот такая.

Реализация

Перед вами псевдокод программной реализации фильтра на триггерах Шмитта. Код пере-использует готовый программный компонент отдельного триггера Шмитта в четырех экземплярах.

static uint8_t SchTrigStateToU8(const SchmittTriggerState_t state) {
    uint8_t val = 0;
    switch (state) {
        case SCHMITT_TRIGGER_STATE_UP:            val = 1;            break;
        case SCHMITT_TRIGGER_STATE_DOWN:            val = 0;            break;
        default:            break;
    }
    return val;
}

static const int32_t StateValLUT[16] = { 0, 1, -1, 2, -1, -1, -1, 3, -1, -1, -1, -1, -1, -1, -1, 4, };

static int32_t hist_filter_state_to_out( HistFilterHandle_t* const Node) {
    int32_t out_sample = 0;
    Node->state.tgrigger0 = SchTrigStateToU8(Node->SchmittTrigger[0].state);
    Node->state.tgrigger1 = SchTrigStateToU8(Node->SchmittTrigger[1].state);
    Node->state.tgrigger2 = SchTrigStateToU8(Node->SchmittTrigger[2].state);
    Node->state.tgrigger3 = SchTrigStateToU8(Node->SchmittTrigger[3].state);
    Node->state.res = 0;
    out_sample = StateValLUT[Node->state.byte];
    return out_sample;
}


int32_t hist_filter_proc_sample(uint8_t num, const float in_sample) {
    int32_t out_sample = 0;
    HistFilterHandle_t *Node = HistFilterGetNode(num);
    if (Node) {
        uint32_t i = 0;
        for (i = 0; i < 4; i++) {
            schmitt_trigger_proc_val_ll(&Node->SchmittTrigger[i], in_sample);
        }
        out_sample = hist_filter_state_to_out(Node);
        LOG_DEBUG(HIST_FILTER, "HIST_FILTER_%u,in:%f,Out:%d",num ,in_sample,out_sample);
    }
    return out_sample;
}

LUT реализует вот эту таблицу

Решение №2

На самом деле задачу можно решить проще на основе конечного автомата из пяти состояний. Шаг 1. Перечисляем все состояния. За состояние примем выходной сигнал.

Шаг второй: перечисляем все входные воздействия. Интерес представляют промежутки однозначности (gap) и зоны гистерезиса (hist).

Шаг третий: строим таблицу переходов. Вот в таблицу переходов мы и заложим эффект триггера Шмитта.

Выход автомата это его состояние. По сути получился автомат Мура. Реализация на основе одного LUT + классификация входа x . Вот так просто и не затейливо.

Проверка работы фильтра

Как можно заметить фильтр убирает высокочастотную составляющую входного сигнала.

Достоинства фильтра на ТШ в его простоте реализации и малом количестве операций на один семпл ( в сравнении с FIR фильтрами). Фильтр на триггерах Шмитта стабильный и не склонен к генерации как IIR фильтры.

Итог

Вот такой простой фильтр. Можно и дальше наращивать квантование добавляя количество отдельных триггеров Шмитта. Надеюсь этот алгоритм пригодится кому-нибудь в разработке трактов обработки сигналов.

Сокращение

Расшифровка

LUT

LookUpTable

ТШ

триггер Шмитта

FIR

finite impulse response

IIR

Infinite impulse response

Источники

Название

URL

Медианный фильтр на двух бинарных кучах

https://habr.com/ru/articles/935750/

Синтез Цифрового БИХ Фильтра Низких Частот

https://habr.com/ru/articles/836830/

Триггер Шмитта

https://ru.wikipedia.org/wiki/Триггер_Шмитта

Аналитика по фильтру

https://docs.google.com/spreadsheets/d/1dAsB0f5b-lm0g4HAjEvebUA9jxHLjEZ7dJht3QiOLoY/edit?gid=0#gid=0

Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.
Вы применяли триггеры Шмитта?
73.91%да17
26.09%нет6
Проголосовали 23 пользователя. Воздержались 5 пользователей.
Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.
Вы делали указанный фильтр на триггерах Шмитта?
8.33%да2
91.67%нет22
Проголосовали 24 пользователя. Воздержались 7 пользователей.