2016-12-16 17 views
2

Я проектирую измерительный прибор, который имеет видимый пользовательский выход на панели 30 светодиодов. Логика программы действует таким образом (псевдокод)Индикатор светодиодной индикации, мерцающий в смежных значениях и как избежать этого (встроенный C)

while(1) 
{ 
(1)Sensor_Read(); 
(2)Transform_counts_into_leds(); 
(3)Send_to_bar(); 
{ 

соответствующая функция (2) представляет собой простой алгоритм, который преобразует отсчеты от датчика I2C до значения, последовательно отправленного перекладывать-регистры, которые управляют единственным светодиоды. Переменная отправляется функции (3) представляет собой просто количество светодиодов, которые должны остаться на (0 Аль светодиодов выключен, 30 для всех светодиодов на)

uint8_t Transform_counts_into_leds(uint16_t counts) 
{ 
float on_leds; 
on_leds = (uint8_t)(counts * 0.134); /*0.134 is a dummy value*/ 
return on_leds; 
} 

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

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

ВОПРОС: Как решение этой проблемы может быть реализовано в моем проекте?

+0

Отправьте код, а не его изображения ..... – LPs

+0

Вы должны начать изучать что-то о [цифровых фильтрах] (https://en.wikipedia.org/wiki/Digital_filter) – LPs

+0

[Moving Average] (https: //en.wikipedia.org/wiki/Moving_average) может быть идеей. – LPs

ответ

3

Гистерезис полезен для ряда приложений, но я бы предположил, что это не подходит в этом случае. Проблема в том, что если уровень действительно падает с 8 до 7, например, вы не увидите никаких изменений, пока, по крайней мере, один образец в 6, и он не перескочит до 6, и там будет образец 8, прежде чем он вернется к 7.

Более подходящее решение в случае является скользящим средним, хотя проще и полезнее использовать сумму перемещения и использовать большее разрешение, которое дает. Например, перемещаемая сумма 16 эффективно добавляет (почти) 4 бит разрешения, что делает 8-битный датчик эффективнее 12 бит - за счет пропускной способности, конечно; вы ничего не получаете. В этом случае более низкой пропускной способности (т.е. менее реагирует на более высокие частоты точно то, что вам нужно)

Moving суммы:

#define BUFFER_LEN 16 ; 
#define SUM_MAX (255 * BUFFER_LEN) 
#define LED_MAX 30 

uint8_t buffer[BUFFER_LEN] = {0} ; 
int index = 0 ; 
uint16_t sum = 0 ; 

for(;;) 
{ 
    uint8_t sample = Sensor_Read() ; 

    // Maintain sum of buffered values by 
    // subtracting oldest buffered value and 
    // adding the new sample 
    sum -= buffer[index] ;    
    sum += sample ; 

    // Replace oldest sample with new sample 
    // and increment index to next oldest sample 
    buffer[index] = sample ; 
    index = (index + 1) % BUFFER_LEN ; 

    // Transform to LED bar level 
    int led_level = (LED_MAX * sum)/SUM_MAX ; 

    // Show level 
    setLedBar(led_level) ; 
} 
+0

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

+0

Рассмотрим, например, относительно бесшумный датчик, скажем, DS18B20 (± 0,5 ° C). Скажем, датчик находится в свободном воздухе, температура воздуха точно равна 19,75 ° C. Некоторые показания будут составлять 19,5 ° C и около 20,0 ° C. Если на выходе используется один бар на 1 ° C, усреднение не удаляет мерцание, потому что каждое считывание (обновление датчика) может изменить среднее значение от ≥20 ° C до ≤20 ° C или наоборот (и не имеет значения, насколько велика/long фильтр). Другими словами, фильтрация не снимает изменение края ножа отображаемого значения. –

+0

@NominalAnimal: Хорошая точка в реализации гистерезиса; ваш ответ применяется. Уменьшая полосу пропускания и, следовательно, скорость, с которой может изменяться значение, скользящее среднее (или любая форма фильтрации нижних частот, если на то пошло) достигнет желаемого результата - уровень может все же, конечно, переключаться между двумя значениями, но только когда это низкочастотный тренд, а не просто шум - вопрос о том, когда «мерцание» становится «желательным сигналом», зависит от приложения и в этом методе может быть определено соответствующей длиной буфера. – Clifford

2

Основная проблема - отображение данных датчиков в удобочитаемом способом - очень интересно. Вот мой подход в псевдокоде:

Loop: 

    Read sensor 
    If sensor outside valid range: 
     Enable warning LED 
     Sleep in a low-power state for a while 
     Restart loop    
    Else: 
     Disable warning LED 

    Filter sensor value 

    Compute display value from sensor value with extra precision: 
     If new display value differs sufficiently from current value: 
      Update current displayed value 

    Update display with scaled-down display value 

Фильтрация касается шума в измерениях. Фильтрация сглаживает любые внезапные изменения в измерении, устраняя внезапные всплески. Это похоже на эрозию, превращая острые и зазубренные горы в скаты и холмы.

Гистерезис скрывает небольшие изменения, но не фильтрует результаты. Гистерезис не влияет на шумные или зубчатые данные, он лишь скрывает небольшие изменения.

Таким образом, эти два являются отдельными, но дополнительными методами, которые влияют на считывание по-разному.

Ниже я опишу два разных фильтра и два варианта простой реализации гистерезиса, подходящих для отображения числовых и гистограмм.

Если возможно, я бы порекомендовал вам написать несколько сценариев или тестовых программ, которые выводят входные данные и различные отфильтрованные выходные данные, и нарисуйте их в вашей любимой программе построения графиков (мой Gnuplot). Или, еще лучше, эксперимент!Ничто не сравнится с практическими экспериментами для материала интерфейса человека (по крайней мере, если вы используете существующие предложения и известную теорию как свою основу, и прыгайте вперед оттуда).


Moving average:

Вы создаете массив N показаний датчиков, обновляя их в циклическом режиме, и используя их среднее значение в качестве текущего чтения. Это дает очень приятные результаты (как в удобных для пользователя, интуитивно понятных) результатах, так как только последние данные датчика показывают, что последние данные датчика влияют на среднее значение.

При первом запуске приложения вы должны скопировать первое чтение во все записи N в массиве усреднения. Например:

#define SENSOR_READINGS 32 

int sensor_reading[SENSOR_READINGS]; 
int sensor_reading_index; 

void sensor_init(const int reading) 
{ 
    int i; 

    for (i = 0; i < SENSOR_READINGS; i++) 
     sensor_reading[i] = reading; 

    sensor_reading_index = 0; 
} 

int sensor_update(const int reading) 
{ 
    int i, sum; 

    sensor_reading_index = (sensor_reading_index + 1) % SENSOR_READINGS; 
    sensor_reading[sensor_reading_index] = reading; 

    sum = sensor_reading[0]; 
    for (i = 1; i < SENSOR_READINGS; i++) 
     sum += sensor_reading[i]; 

    return sum/SENSOR_READINGS; 
} 

При запуске, вы звоните sensor_init() с очень первым действительным чтения датчика и sensor_update() со следующими показаниями датчика. sensor_update() вернет отфильтрованный результат.

Вышеуказанное работает лучше всего при регулярном опросе датчика, а SENSOR_READINGS можно выбрать достаточно большим, чтобы должным образом отфильтровать любой нежелательный шум в показаниях датчика. Разумеется, для массива требуется оперативная память, которая в некоторых микроконтроллерах может оказаться недостаточной.


Exponential smoothing:

Когда не хватает оперативной памяти для использования скользящего среднего для фильтрации данных, экспоненциальный сглаживающий фильтр часто применяется.

Идея состоит в том, что мы сохраняем среднее значение и пересчитываем среднее значение с использованием каждого нового считывания датчика с использованием (A * average + B * reading)/(A + B). Влияние каждого показания датчика на средние затухания экспоненциально: вес самого текущего показания датчика всегда равен B/(A+B), вес предыдущего составляет A*B/(A+B)^2, вес одного до этого составляет A^2*B/(A+B)^3 и т. Д. (^ с указанием возведения в степень); вес n-го датчика, прошедшего в прошлом (с токовым n=0) - A^n*B/(A+B)^(n+1).

код, соответствующий предыдущий фильтр теперь

#define SENSOR_AVERAGE_WEIGHT 31 
#define SENSOR_CURRENT_WEIGHT 1 

int sensor_reading; 

void sensor_init(const int reading) 
{ 
    sensor_reading = reading; 
} 

int sensor_update(const int reading) 
    return sensor_reading = (sensor_reading * SENSOR_AVERAGE_WEIGHT + 
          reading * SENSOR_CURRENT_WEIGHT)/
          (SENSOR_AVERAGE_WEIGHT + SENSOR_CURRENT_WEIGHT); 
} 

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


Применение Гистерезис:

(В этом разделе, в том числе, например, код, отредактированный на 2016-12-22 для ясности.)

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

Существует два типичных варианты того, как гистерезис применяется к показаниям: фиксированный и динамический.Фиксированный гистерезис означает, что отображаемое значение обновляется всякий раз, когда значение отличается фиксированным пределом; динамический означает, что лимиты установлены динамически. (Динамический гистерезис намного реже, но он может быть очень полезным в сочетании со скользящим средним, можно использовать стандартное отклонение (или шкалы ошибок) для установки пределов гистерезиса или установить асимметричные пределы в зависимости от того, будет ли новое значение меньше или больше, чем предыдущий.)

Фиксированный гистерезис очень прост в применении. Во-первых, поскольку нам нужно применить гистерезис к значению более высокой точности, чем выход, мы выбираем подходящий множитель. То есть display_value = value/DISPLAY_MULTIPLIER, где value - это значение фильтра, которое может быть отфильтровано, и display_value - это отображаемое целое значение (например, количество баров).

Обратите внимание, что ниже, display_value и значение, возвращаемое функциями, см. Отображаемое целое значение, например, количество световых индикаторов. value - это (возможно, фильтрованное) показание датчика, а saved_value содержит показание датчика, которое в настоящее время отображается.

#define DISPLAY_HYSTERESIS 10 
#define DISPLAY_MULTIPLIER 32 

int saved_value; 

void display_init(const int value) 
{ 
    saved_value = value; 
} 

int display_update(const int value) 
{ 
    const int delta = value - saved_value; 
    if (delta < -DISPLAY_HYSTERESIS || 
     delta > DISPLAY_HYSTERESIS) 
     saved_value = value; 
    return saved_value/DISPLAY_MULTIPLIER; 
} 

delta просто разница между новым значением датчика, а значение датчика, соответствующий значению отображаемого в данный момент.

Эффективный гистерезис в единицах отображаемого значения составляет DISPLAY_HYSTERESIS/DISPLAY_MULTIPLIER = 10/32 = 0.3125. Это означает, что отображаемое значение может быть обновлено три раза до того, как будет видно видимое изменение (если, например, медленно уменьшается или увеличивается, больше, если значение просто колеблется, конечно). Это устраняет быстрое мерцание между двумя видимыми значениями (когда значение находится в середине двух отображаемых значений), но гарантирует, что погрешность показания меньше половины единиц отображения (в среднем, половина плюс эффективный гистерезис в худшем случае).

В реальном приложении жизни, обычно используют более полную форму return (saved_value * DISPLAY_SCALE + DISPLAY_OFFSET)/DISPLAY_MULTIPLIER, который масштабирует отфильтрованное значение датчика по DISPLAY_SCALE/DISPLAY_MULTIPLIER и перемещает нулевую точку DISPLAY_OFFSET/DISPLAY_MULTIPLIER, оба оценивали при 1.0/DISPLAY_MULTIPLIER точности, но только с использованием целочисленных операций , Однако для простоты я просто предполагаю, что для вывода отображаемого значения value, скажем, количества освещенных светодиодных баров, вы просто разделите значение датчика на DISPLAY_MULTIPLIER. В любом случае гистерезис составляет DISPLAY_HYSTERESIS/DISPLAY_MULTIPLIER выходного блока. Соотношения от 0,1 до 0,5 работают нормально; и ниже тестовых значений, 10 и 32, дает 0,3125, что примерно на полпути диапазона коэффициентов, которые, как я считаю, работают лучше всего.

Динамический гистерезис очень похож на выше:

#define DISPLAY_MULTIPLIER 32 

int saved_value_below; 
int saved_value; 
int saved_value_above; 

void display_init(const int value, const int below, const int above) 
{ 
    saved_value_below = below; 
    saved_value = value; 
    saved_value_above = above; 
} 

int display_update(const int value, const int below, const int above) 
{ 
    if (value < saved_value - saved_value_below || 
     value > saved_value + saved_value_above) { 
     saved_value_below = below; 
     saved_value = value; 
     saved_value_above = above; 
    } 
    return saved_value/DISPLAY_MULTIPLIER; 
} 

Обратите внимание, что, если DISPLAY_HYSTERESIS*2 <= DISPLAY_MULTIPLIER, отображаемое значение всегда в блоке отображения фактической (отфильтрованной) значения датчика. Другими словами, гистерезис может легко справляться с мерцанием, но ему не нужно добавлять много ошибки к отображаемому значению.

Во многих практических случаях наилучшее количество применяемого гистерезиса зависит от количества кратковременных изменений в образцах датчиков. Это включает в себя не только шум, но и типы сигналов, которые необходимо измерить. Гистерезис всего 0,3 (относительно выходного блока) достаточен для полного устранения мерцания, когда показания датчика отражают значение фильтрованного датчика между двумя последовательными целыми числами, которые сопоставляются с разными целыми числами, поскольку это гарантирует, что значение фильтрованного датчика должно измениться на менее 0,3 (в единицах отображения вывода), прежде чем он повлияет на изменение дисплея.

Максимальная погрешность с гистерезисом - это половина дисплея и текущий гистерезис. Половина единицы - минимальная возможная ошибка (так как последовательные единицы разделены на одну единицу, поэтому, когда истинное значение находится посередине, любое показанное значение верное для половины единицы). При динамическом гистерезисе, если вы всегда начинаете с некоторого фиксированного значения гистерезиса, когда показание изменяется достаточно, но когда показание находится внутри гистерезиса, вместо этого вы просто уменьшаете гистерезис (если больше нуля). Такой подход приводит к правильному отслеживанию значения датчика (максимальная ошибка составляет половину единицы плюс начальный гистерезис), но относительно статическое значение отображается как можно точнее (при максимальной ошибке на половину единицы). Я не показываю пример этого, потому что он добавляет еще одну настраиваемую (как гистерезис распадается к нулю), и требует, чтобы вы сначала проверили (откалибровали) датчик (включая любую фильтрацию); в противном случае это похоже на полировку камыша: возможно, но не полезно.

Также обратите внимание, что если у вас есть 30 баров на дисплее, вы на самом деле 31 состояния (ноль баров, один бар, .., 30 бар), и, таким образом, подходящий диапазон для value является 0 до 31*DISPLAY_MULTIPLIER - 1 включительно.

+0

Очень exaustive ответ. – LPs

+0

Большое спасибо за ваш исчерпывающий ответ. Раньше я использовал скользящее среднее для инструментов, которые использовали 7-сегментный интерфейс для вывода. В этом случае сглаживание создавало требуемый эффект мягкого перехода между значениями. Btw Я никогда не использовал гистерезис, поэтому я прошу, если вы можете включить некоторые комментарии в примеры гистерезиса, чтобы сделать его более понятным (я не совсем понял, что такое 'const int value' и' DISPLAY_MULTIPLIER' используется) – danstm

+1

@danstm: Could пожалуйста, перечитайте раздел * Применение гистерезиса: * *? Я сильно отредактировал его, переименовал пару переменных в примеры и описал их смысл/использование в тексте; Я хотел бы знать, имеет ли смысл, или мне нужно переписать эту часть, чтобы объяснить ее лучше. Короче говоря, «значение» - это отфильтрованное показание датчика, «saved_value» - это отфильтрованное показание датчика, которое в настоящее время отображается, а возвращаемое значение из функций - отображаемое целое значение. 'DISPLAY_MULTIPLIER' - простой масштабный коэффициент: * display_value * = * sensor_value * /' DISPLAY_MULTIPLIER'. –