Pull to refresh

Comments 83

Неплохо вышло, но можно код упростить.


Первое что бросается в глаза — это применение -1 ко всем индексам в таблицах. Мало того что не так красиво выглядит как могло бы, так ещё и опасно: легко забыть этот самый -1 написать и всё, привет крайне странная ошибка.


Второе — а требуется ли вообще представление конечного автомата в памяти? Почему бы не сделать как-то так:


static bool button_handle_input_passive(Button_t* Node) {
    switch (Node->state) {
        case BUTTON_STATE_UNPRESSED: return true;
        case BUTTON_STATE_PRESSED:
            Node->state = BUTTON_STATE_UNPRESSED;
            Node->short_pres_cnt++;
            LOG_DEBUG(BUTTON, "ShortPress %s, %u ms", GpioPad2Str(Node->pad.byte),Node->time_ms);
            Node->time_ms = 0;
            return button_run_callback(Node, BUTTON_PRESS_SHORT);

        case BUTTON_STATE_PRESSED_PROCESSED:
            Node->state = BUTTON_STATE_UNPRESSED;
            Node->time_ms = 0;
            return true;
    }
    return false;
}

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

Почему вы ждете на входе конкретный объект кнопку а в коде у вас какая то обстрактная нода по итогу. Почему нельзя нормальный нэйминг использовать button, input_button, etc.

Потом смотришь исходники в чужих проектах и ничего не понятно что в коде происходит, приходится по 10 раз сверяться что же именно все таки в подобных переменных node, i, x, y лежит на самом деле.

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

Почему вы ждете на входе конкретный объект кнопку а в коде у вас какая то обстрактная нода по итогу. Почему нельзя нормальный нэйминг использовать button, input_button, etc.

Потом смотришь исходники в чужих проектах и ничего не понятно что в коде происходит, приходится по 10 раз сверяться что же именно все таки в подобных переменных node, i, x, y лежит на самом деле.

Код надо писать единообразно безобразно.
https://habr.com/ru/articles/679256/

Не надо писать код безобразно!


По вашей же ссылке: "29–Давайте переменным осмысленные имена".

Второе — а требуется ли вообще представление конечного автомата в памяти?

Дык, код функций, он тоже в памяти лежит же.

Ещё бы обоснование было какое-то у этого правила — было бы вовсе замечательно.


А обоснование в стандарте ISO26262 тоже присутствует. Код должен быть ремонтопригодным.

Когда в каждой функии только один return, то всегда можно приписать в конце функции ремонтный код и этот код точно будет выполнен.

ремонтный код

WTF ремонтный код? Может, имеется в виду дебажный printf? ;)

WTF ремонтный код? Может, имеется в виду дебажный printf? ;)

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

Это уже зависит от обстоятельств.

Неубедительно как то ;) Какие могут быть обстоятельства для ремонтного кода? Что-то сломалось и его ремонтируют? ;)

Да, значение результата на 15 отличается от нужного, но всегда. Мы вместо погружения в дебри перед единственным ретурном пишем -15 и радостно живем дальше.
Живые примеры мне встречались, когда это оправдано, но естественно редко. И давно, поэтому не вспомню.

Мы вместо погружения в дебри перед единственным ретурном пишем -15 и радостно живем дальше.

Какой ужас :( Кровью написано, значит.. Забили костыль, и теперь то самолет точно не упалет.

НЯП early return используют для аварийного завершения функции и возвращают ошибку. Это пожалуй самое разумное использование нескольких точек выхода. В этом случае тоже ничто не мешает умножать правильный результат на -15, а код ошибки не умножать.
Хотя если мы провели тестирование, и увидели, кто результат нужно корректировать всегда — что нам мешает исправить алгоритм? Может у нас наоборот, тесты неправильно работают, или не все значения покрывают?

Ну как бы "выбросить исключение" и "ранний возврат" это разные вещи.
В моей практике в трети где-то случаев ранний возврат — это просто движение по наиболее вероятному сценарию. Типа ответ есть в объекте — возвращаем. Потом можно поискать в связанном: есть — возвращаем.
На крайний случай — хитрый длинный алгоритм с глубоким поиском. Нашли — вернули. Не нашли — вернули пустышку или ошибку.

движение по наиболее вероятному сценарию

Тоже хорошо. В любом случае это делает код чище и понятнее. А вот требование иметь место для вкрячивания костыля ремонтного кода — странное. Ну, надо значит надо. _разводитруками

…а теперь посмотрите ещё раз на функцию, и скажите: какой такой ремонтный код вам может понадобиться дописать ко всем веткам сразу?


А теперь ещё более интересный вопрос: допустим, что есть ситуация, в которой вам и правда потребуется "ремонтный код" после окончания обработки сигнала PASSIVE. Как вы его добавите в ваш исходный конечный автомат, в конец какой функции? А никак, этого места там не предусмотрено. По крайней мере, единого места — так точно.




Я совсем не против поддерживаемости кода (именно так правильно переводится maintainability). Но правило "одного return" не имеет к этому свойству никакого отношения, это просто тупая бюрократия.

Но правило "одного return" не имеет к этому свойству никакого отношения, это просто тупая бюрократия.


Правила ISO26262 написаны кровью.

Некоторые правила похожи на ритуалы.

Когда в каждой функии только один return, то всегда можно приписать в конце функции ремонтный код и этот код точно будет выполнен.

или переместить код исходной функции ф() в новую функцию ф1(),

а в ф() вызвать ф1() и после нее - все что вам угодно.

во-вторых - в том скрине на который вы ссылаетесь, еще пишут "никаких динамических объектов или переменных" - это весьма интересное ограничение!

Угу, найти все вызовы Ф и заменить на Ф1, особенно когда это библиотечный код, и не пойми кто с ним уже слинкован в соседнем отделе.

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

"никаких динамических объектов или переменных" - это весьма интересное ограничение!

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

Первое что бросается в глаза — это применение -1 ко всем индексам в таблицах. Мало того что не так красиво выглядит как могло бы, так ещё и опасно: легко забыть этот самый -1 написать и всё, привет крайне странная ошибка.

Хорошо. Исправил.

Второе — а требуется ли вообще представление конечного автомата в памяти? Почему бы не сделать как-то так:

Будет больше строк кода по сравнению с LookUp таблицами. Да и с LUT быстрее будет работать.

Тем более что LUT(ы) они тоже в Flash памяти микроконтроллера так как там прописано const.

Будет больше строк кода по сравнению с LookUp таблицами. Да и с LUT быстрее будет работать.

Первое — возможно, но только при большом количестве одинаковых действий в ответ на разные входные сигналы.


А вот насчёт "быстрее будет работать" — за счёт чего? Конструкция switch-то за кадром в такой же LUT компилируется.

Третье упрощение применимо к функции button_run_callback. Она вызывается ровно в двух местах кода, и в каждом из них совершенно точно известно какая именно функция вызывается.


Так почему бы не передать аргументом сразу эту функцию?


static bool button_run_callback(Button_t* Node, ButtonIsrHandler_t press_handler) {
    bool res;

    if(!press_handler) {
        LOG_ERROR(BUTTON, "NoHandler4 %s", ButtonPressType2Str(press_type));
        return false;
    }

    if (!is_flash_addr((uint32_t)press_handler)) {
        LOG_ERROR(BUTTON, "HandlerOutOfFlash 0x%p", press_handler);
        Node->err_cnt++;
        return false;
    }

    LOG_INFO(BUTTON, "HandlerInOfFlash 0x%p", press_handler);
    Node->handler_cnt++;
    return press_handler();
}

Можно проще:

constexpr uint8_t DEBOUNCE_TICKS = 40;
constexpr uint8_t LONG_PRESS = 200;  

struct Button {
    uint8_t port;
    uint8_t mask;
    uint8_t cool_down = 0;
    uint8_t pressed_for = 0;

    void (*on_press)(bool is_long);

    void tick() {
        if (cool_down > 0) {
            --cool_down;
            return;
        }
        if ((port & mask) != 0) { // not pressed
            if (pressed_for == 0) return;  // and was not pressed 
            on_press(pressed_for >= LONG_PRESS);
            pressed_for = 0;
        } else if (pressed_for < LONG_PRESS) {
            pressed_for++;
        }
        if (pressed_for < 2)
            cool_down = DEBOUNCE_TICKS;
    }
};

Ну и "дебаунс"(дребезг) компенсировать аппаратно, пассивным RC фильтром :)

RC фильтр денег стоит. А 10 строк кода — почти бесплатно ;)

Видимо в Самсунгах, Сименсах и иже полные дураки сидят, что ставят на кнопки RC фильтры. Или?
зы даже у автора в иллюстрации к статье RC фильтр на кнопке присутствует...

Сильное утверждение требует сильных доказательств ;)

Вот вам кусок схемы телевизора Samsung.

Кнопки без конденсаторов ;)
image

Чем Вам схема платы Nucleo (иллюстрация к статье) и её кнопки Blue не доказательство? Может Nucleo выходит в меньших количествах, чем телевизоры Самсунг, но это тоже массовая продукция. И вот прямо вся продукция Самсунг идёт без rc фильтров?

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

Чем Вам схема платы Nucleo (иллюстрация к статье) и её кнопки Blue не доказательство?

Доказательство чего? ;) Так то с демо-платой будут играться разные люди, в том числе и непряморукие. А в процессоре телевизора, очевидно, уже есть антидребезг.


И вот прямо вся продукция Самсунг идёт без rc фильтров?

 - Настоящий шотландец всегда ставит RC-фильтр!
 - Вот шотландец без фильтра
 - Это ненастоящий шотландец!

:D


зы и это вроде пульт ду?

COLOR TELEVISION RECEIVER
Chassis: KS1A(P)_Rev.1


Здоровенная простыня. Я просто отрезал кусок с кнопками ;)

Вообще ничего не понял, кроме того, что не угадал с типом устройства. Давайте последовательно:

  • Вы мне пишете: "Сильное утверждение требует сильных доказательств ;)" Пункт 1. Я отвечаю, что Nucleo прекрасный пример использования RC фильтров для антидребезга в массовой продукции.

  • Следом от Вас идет: Доказательство чего? ;) Мой ответ может быть только - смотри Пункт 1.

А телевизорами Самсунг пользуются исключительно пряморукие люди?
Можете не отвечать. Согласен, Вы Д'Артаньян и Самсунг (на основании одного куска схемы и Вашей шуточки) использует исключительно программный дебаунс.

Nucleo прекрасный пример использования RC фильтров для антидребезга в массовой продукции.

Это не массовая продукция, а отладочная плата. Не путайте.

использует исключительно программный дебаунс.

А какой смысл в аппаратном то, кроме удорожания продукции?

Пролистайте немного вверх что ли..


Я утверждал только это. 10 строчек кода имеют цену около 0, особенно если они растиражированы на 100500 устройств. RC цепочка добавляет копеечку в каждое устройство. А почему вы решили, что абсолютно все должны всё делать именно как в Nucleo, или именно как в телевизоре, а кто делает иначе тот неправ — я правда не знаю ;)

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

Ей богу, троллинг какой-то. Пусть будет по вашему. Хорошего дня.

Так тут кнопки через АЦП работают, потому там и нет RC цепей, весь дребезг компенсируется скоростью оцифровки. Но вообще да, RC цепочки нечасто встречаются в технике (я в свои устройства так вообще никогда их не ставлю на кнопки), дребезг без проблем компенсируется программно.

А вас не смущает что в этой схеме кнопки заводятся не на GPIO, а на ADC! Тут нет большой нужды фильтровать дребезг аппаратно.

Тут нет большой нужды фильтровать дребезг аппаратно.

Обоснуете? ADC он достаточно быстрый и способен уловить дребезг.

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

делать единичные замеры с интервалом "нечувствительным" к дребезгу и уже их анализировать?

Так и получился софтовый подавитель дребезга ;)

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

Можно наверное, как-то извратиться и сработать с каждым из компараторов (битов) АЦП аппаратно, в зависимости от его номера.

Учитывая то, что на схеме представлен явно заказной МК, то в него вполне могли добавить программируемые компараторы на АЦП. Хотя особого смысла в этом и нет.

даже на старинных МК АЦП однозначно словит дребезг.

Усреднение показаний в помощь.

Не вопрос. Опять десятый раз оговорюсь, могу ошибаться. Но из даташита более менее знакомого мне PIC12F675, 20 летней давности МК с 64 БАЙТАМИ оперативки и 1 килословом флэша. 44 страница таблица 7-1 самый продолжительный такт АЦП 51.2 мкс (при частоте проца 1.25МГц, очень экзотично, обычно их тактуют на 4МГц), для одного измерения надо 11 тактов, итого 563.2 мкс. Что сильно ниже указанного Вами интервала "нечуствительности" в 50-100мс.
Словит он дребезг? Словит.
Я не разбираюсь сильно, но если заблуждаюсь, то можно разобрать мои заблуждения на данном примере.

А кто мешает проводить измерение раз в 10 мс, к примеру, и делать усреднение по 8 измерениям?

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

Получится программная реализация RC цепочки ;)

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

По этому перед ADC должен стоять, как минимум, RC фильтр с Τ примерно одного порядка с периодом ADC.

RC фильтр перед GPIO, кстати, эффективен только в комплекте с триггером шмитта на входе MCU.

Хмм, как применение и эксплуатация li-ионных батарей кореллирует с темой Вашей статьи? Только не через мой комментарий, пожалуйста.

Зы А вообще и Тесла горит и взрывается, и Шаттлы с экипажами падают и даже "Луна 25" от самых лучших в мире специалистов, которые никогда не ошибаются, в отличии от всяких там Самсунгов, тоже вот что-то не заладилось :)))))

Т.е. у компании, которая на рынке не один десяток лет и выпускает тысячи единиц продукции ежедневно, ошибка в одном устройстве позволяет сделать вывод о полной некомпетентности разработчиков? Серьезно?

Вот как раз в таких компаниях и возникает мнимая уверенность в абсолютной правоте, которая приводит к фейлам.

В любой компании бывают ошибки. Вспомнить тот же отзыв автомобилей.

В реальной жизни все иначе - stm32 имеет пулап 10ком. Не использовали. Емкость на кнопке есть, но годы спустя кнопки окисляются до состояния такого шума при нажатии, что емкость не разрядят. Поэтому в чистоган надо только кнопку и только ногу с пулапом. И опять же - про прерывание в начале статьи заикнулись а где оно в коде? EXTI определения нет. В этом случае ловим прерывание, снимаем прерывания для ноги и проверяем дальнейшие состояния опросом.
Также нужно понимать реакцию кода на событие. Если действие должно мгновенно получить отклик - пикнуть и включить нечто при нажатии, например - то потребуется чуть более хитрая логика обработки событий.
А для устранения дебонса самым правильным были кнопки с переключением на два контакта.
Лучшее, что мне удалось в коде на EXTI собрать - емкостные кнопки - они вообще не дребезжат, паяются на плате и тач контакт с обратной стороны работают, а чип стоик как механическая кнопка

Выглядит, как оверинжиниринг. В реальных проектах делается гораздо проще. Вот пример псевдокода для отдельного потока. Просто, не требует ресурсов, легко поддерживается и легко читается.

  1. Сигнал на GPIO есть? если нет, то иди на 1

  2. пауза в Н миллисекунд

  3. сигнал на GPIO есть? если нет, то иди на 1

  4. кнопка_нажали_коротко = да

  5. пауза в М миллисекунд

  6. кнопка_нажали_коротко=нет

  7. сигнал на GPIO есть? если нет, то иди на 1

  8. копка_нажали_длинно=да

  9. сигнал на GPIO есть? если да, то иди на 9

  10. кнопка_нажали_длинно=нет

  11. иди на 1.

Вот пример псевдокода для отдельного потока.

А что если это NoRTOS сборка?

Ваш алгоритм при длинном нажатии зарегистрирует и короткое нажатие, и длинное.

Со стороны пользователя получится так.
Нажал, подержал 10 сек, отпустил. А в прошивке отработало два обработчика: на короткое и на длинное.

Выполнились два несовместимых действия и приложение упало.


Никто не мешает перед установкой флага короткого нажатия делать еще одну проверку состояния кнопки. Если все еще нажата, то флаг короткого нажатия не ставится. Т.е. по сути, короткое нажатие будет срабатывать п отпусканию кнопки.

  1. пауза в М миллисекунд

"И пусть весь мир подождет!" (с)

ИМХО. Конечнный автомат, это, конечно круто, но.. ЗАЧЕМ так сложно?
Берём массив кнопок. Каждый квант времени (в случае моих устройств - каждую милисекунду, сразу после обработчика SysTick, возвращаемся в main() и читаем состояния кнопок (либо внутри самого SysTick'а) проверяем состояние пинов.

Если надата - увеличиваем счётчик. Если отпущена - сбрасываем (ееееее, антидребезг!). Если при чтении нуля с пина в соответствующей ячейке массива значение больше порогового для кнопки - в другой массив кладём "1". Если же досчитали до длительного нажатия - "2". Если превышен порог залипания - "3" или "0" - в зависимости от желания. Всё. Особого конечного автомата нет, а код видит нажатие кнопки.

А если читать состояние раз в 50-100 мс, то о дребезге можно вообще забыть...

Можно. А считая ещё реже можно пропустить нажатие.

Как показывает практика, опроса кнопок раз в 100 мс достаточно для нормального опроса кнопок без пропусков. Вряд ли у кого-то получится отпустить кнопку быстрее.

А если не удалось опросить кнопку как раз в момент дребезга? Со счетчиками надежнее как то.

На практике такого не бывало никогда. Кнопка по любому будет удерживаться более 100 мс..

Но чтобы определить что кнопка удерживается более 100 мс — все равно нужен счетчик же?

Тут нужен, но к подавлению дребезга он отношения не имеет же.

Ваш алгоритм при длинном нажатии зарегистрирует еще и короткое.
Получится нажал, подержал 10 сек, отпустил и сработало два обработчика.

Если в массиве состояния уже "2", то "1" туда складывать нет смысла.

А сбросом этого значения будет заниматься основной поток. Так что нет.
Ну и мелкие проверки "на идиота" никто не отменял, а расписывать их все без псевдокода сильно долго.

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

В каком редакторе такие красивые схемы переходов нарисовали?

В каком редакторе такие красивые схемы переходов нарисовали?


Спасибо. Это inkscape.
https://inkscape.org/



Бесплатная, свободная кроссплатформенная программа для составления векторной графики и даже чертежей!

inkscape бурно развивается уже много лет и стабильно появляются новые релизы.

В inkscape есть слои, цвета, привязки, стили линий, стили концов линий и много чего ещё.

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

Чет как-то все переусложнено.

Делаем два массива для кнопок - счетчики и состояния. В состояниях могут быть значения: "свободна", "процесс антидребезга", "нажата коротко", "отпущена после короткого нажатия", "нажата долго", "отпущена после долгого нажатия", "обработана".

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

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

Sign up to leave a comment.

Articles