#ретроспектива

Программисты микроконтроллеров регулярно занимаются починкой багов. Более того 60%-80% работы программиста - это как правило починка багов. Часто программистов нанимают как раз только для того чтобы чинить чужие баги.

Терминология

Баг (bug) - ошибка в компьютерной программе. Несоответствие между реальным поведением программы и ожидаемым поведением программы.

Гейзенбаг (плавающая ошибка) - программная ошибка, которая исчезает или меняет свои свойства при попытке её обнаружения.

В чём проблема?

1--Проблема в том, что все баги они уникальные! Поиск причины каждого конкретного бага это скорее искусство. Починка багов сродни работы детектива. Ты следуешь по следам, мыслишь последовательно, фокусируешься, делаешь гипотезы, ставишь эксперименты, доказываешь или опровергаешь предположения. Делаешь заключения.

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

Причины аппаратных ошибок

1

схемотехник забыл подать питание на микроконтроллер

2

в схему заложили микросхемы с инверсными логическими уровнями

3

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

4

перепутали ориентацию аккумуляторного разъёма на 180 градусов

5

поставили слишком маленькую емкость в цепи питания

6

в закупках оказалиcь десятки бракованных микросхем переходников UART-RS485

7

на плате так много микросхем, что при инициализации проседает напряжение и MCU перезагружается.

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

3--Ошибки которые редко воспроизводятся: Гейзенбаги.

При хорошем DevOps можно отловить много багов на стадии статического анализатора, или отработки скриптов сборки (Make, CMake). Потом можно отловить баги на фазе препроцессора (утилита cpp.exe), затем отловить баги на фазе компилятора (gcc.exe). Ошибки также показывает компоновщик (ld.exe). Часть ошибок могут отловить модульные тесты уже в run-time(е). Последний рубеж обороны от ошибок это интеграционные тесты.

Однако тем не менее ошибки просачиваются в релизные артефакты (*.elf, *.bin, *.hex ) и обнаруживаются только на фазе исполнения программы (run-time(е)).

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

#

Пример программной ошибки

1

Прошивка свалилась в Hard Fault ISR

2

Прошивка постоянно непрерывно перезагружается

3

Не проходит инициализация какого-то программного компонента. Например FatFs.

4

По BLE приходит хрипящий звук

5

На выходе расшифровщика не понятный текст, а кракозябры

6

LoRa радио трансивер не отвечает на команды

7

Подали питание и Heart Beat LED не мигает

8

Не отвечает UART-CLI

9

Печатаешь текст и на экране ничего не происходит. Через 20сек разом вываливается большой текст.

Вот некоторые типичные причины программных багов.

Причина программной ошибок

1

Произошло переполнение типа данных

2

Неверно подобранные приоритеты потоков в RTOS

3

Переполнение стековой памяти

4

Попытка прописать read only память (.rodata)

5

Произошло деление на ноль

6

Произошло обращение к нулевому указателю

7

Сработало прерывание для которого нет обработчика

8

Произошел выход за границы массива. Код перетер чужую RAM память за пределами массива. Глобальные переменные больше не валидные. Последствия непредсказуемы...

9

Неверный конфиг для программного компонента

10

Кто-то закомментировал кусок кода, который должен был исполняться

11

Программа застряла в бесконечном цикле while(1)

12

Функция вычисления формулы вернула NaN вместо double

13

Неверный порядок инициализации

14

Неинициализированная переменная

15

Сработало прерывание SysTick таймера до инициализации планировщика RTOS

Есть ли универсальная методичка на то, как чинить баги?

Да, я считаю, что можно в какой-то степени обобщить алгоритм поиска программной ошибки.

Фаза № 1. Подробно опишите программную ошибку

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

Фаза № 2. Научиться стабильно воспроизводить баг *

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

Чтобы отлаживать код, его надо исполнять. Если в коде баг и нет возможности исполнить код, то баг не исправить.

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

Чтобы лёд тронулся надо сперва научиться регулярно воспроизводить баг, желательно автоматически из какого-нибудь скрипта. Если Вам сказали, что обнаружен баг, то первый вопрос, который вы должны задать себе это: "Настолько стабильно баг воспроизводится?".

Фаза №3 Выдвинете предположения

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

Фаза №4. Делаете эксперименты

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

Фаза №5. Активировать более глубокий уровень логирования

Включите уровни логирования того компонента в котором возникла ошибка до уровня Debug или Paranoid. В хорошей прошивке, должен быть UART-CLI. CLI позволяет включать/отключать уровни логирования для каждого программного компонента. Вероятно баг вызван тем, что не выполняется какой-то важный код, который должен выполняться.

Фаза №6. Прогоните модульные тесты.

Покройте функционал модульными тестами. Это не формальность. Модульные тесты являются скрепами, которые сдерживают корректный функционал при перестройке программы. Очень вероятно, что отчет модульных тестов в UART-CLI сузит место поиска причины бага, либо явно укажет на причину бага. Модульные тесты это отличный способ отладить большой кусок кода, который невозможно или проблематично пройти пошаговым отладчиком.

Фаза №7. Пройдите код пошаговым GDB отладчиком

Это крайняя мера. Проходите код пошаговым отладчиком. Каждую строчку. Анализируйте по какой ветви происходит исполнение кода. Правильные ли значения лежат в локальных и глобальных переменных? Вероятно не выполняется нужная ветвь кода. Пошаговая отладка это весьма утомительное мероприятие. Особенно когда приходится делать её из CLI.

Фаза №8. Прогоните код через статический анализатор.

Это скорее профилактическая мера. Есть такие утилиты, которые анализируют код без его исполнения. Это например CppCheck.exe. Они часто находят нелепые опечатки, которые не замечает компилятор. Минимум раз в месяц надо прогонять код репозитория через статический анализатор. Сами вы всё не уследите.

Фаза №9. Задайте вопрос на профессиональных форумах и сообществах.

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

Итоги

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

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

По крайней мере с этим можно будет дальше работать. А просто разглядывать код с умным видом в надежде найти причину бага - это бессмысленное занятие.

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

Если есть, что добавить, то пишите в комментариях.

Links

16 Способов Отладки и Диагностики FirmWare

Пошаговая GDB отладка ARM процессора из консоли в Win10

Модульное Тестирование в Embedded

Почему Нам Нужен UART-Shell?

11 Aтрибутов Хорошего Firmware

Модульное тестирование для микроконтроллеров https://www.youtube.com/watch?v=-hM38-JDt8c&t=1653s

Вопросы

Какой самый сложный программный баг Вам удавалось починить? В чем, собственно, было дело?

Комментарии (40)


  1. adeshere
    03.01.2024 01:47
    +6

    Фаза № 2. Научиться стабильно воспроизводить баг *

    А куда ведет звездочка? У меня - никуда :-(

    Конечно, лучше быть богатым и здоровым. Только вот это не всегда выполнимо :-(

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

    простейшем локально работающем коде

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

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


    1. gev
      03.01.2024 01:47
      +1

      Фаза № 2. Научиться стабильно воспроизводить баг *

      А куда ведет звездочка? У меня - никуда :-(

      Это – баг)


    1. Rusrst
      03.01.2024 01:47
      +3

      Это было захватывающе, спасибо за пример.


  1. dyadyaSerezha
    03.01.2024 01:47
    +2

    Был один баг у большого и очень важного клиента, который никак не воспроизводился у нас локально. После нескольких недель мучений и наконец-то организованной в выходные видеосессии с клиентом, кучей трассировочных логов и прочего удалось выяснить, что внутрифирменный вебсайт у клиента строился каким-то их визуальным конструктором сайтов из кирпичиков, и в результате главная страница состояла из более чем сотни фреймов, каждый из которых это довольно большое дерево объектов. А наша прога прерывала загрузку сайта, лазила по этим фреймам и деревьям, и создавала у себя аналогичные деревья через RPC и serialisation/deserialisation. И наш код сериализации не учитывал, что в дереве есть много ссылок на один объект (а внутри объекта еще много и т.д.) и получался офигительный оверхед по памяти и времени. В итоге их сайт грузился полторы-две минуты вместо секунд 7, если без нашей проги. Контракт был на волоске, но в последние выходные мы все же успели (у клиента было землетрясение и они не смогли вовремя расторгнуть контракт). А контракт был на несколько млн баксов.


    1. aabzel Автор
      03.01.2024 01:47
      +1

      Был один баг у большого и очень важного клиента, который никак не воспроизводился у нас локально


      Гейзенбаг (плавающая ошибка) - программная ошибка, которая исчезает или меняет свои свойства при попытке её обнаружения.


      1. dyadyaSerezha
        03.01.2024 01:47
        +1

        Не тот случай. У нас не было доступа к их вебсайту - гребаная секретность.


  1. Robastik
    03.01.2024 01:47
    +1

    Самый сложный баг - тот, который не удалось отловить)


    1. dyadyaSerezha
      03.01.2024 01:47

      Как неуловимого Джо)


  1. smirnfil
    03.01.2024 01:47
    +2

    А что вы собираетесь делать с багами, которые не воспроизводятся? Спихивать на QA/Support/Product/Marketing/HR/Board фразой "приходите, когда сможете воспроизвести"? У меня в этом году была пара очень неприятных багов связаных с параллелизмом, которые удалось пофиксить только медитациями на код и попытками понять, что там может пойти не так. Да после того, как я понял, в чем была проблема мне удалось придумать алгоритм воспроизведения, но до этого оно падало только при совпадении определенных условий, которые случались только в продакшене.


    1. Rusrst
      03.01.2024 01:47
      +3

      Да, многопоточность она такая. Но там даже если не падает, не факт что проблем нет на самом деле


  1. Indemsys
    03.01.2024 01:47
    +4

    У разработчиков встраиваемых систем программные ошибки часто переплетаются с аппаратными проблемами. И тут все становиться сложнее.
    Вот например мой недавний кейс с мертвым временем. Решение остается неоднозначным таже когда все вроде бы ясно.
    Будет ли ошибкой если у вас в сиcтеме с жестким реальным временем проскакивают микросекундные незапланированные задержки в ШИМ-е? Как бы и ничего страшного не происходит, все остается рабочим, но возникают дополнительные стрессы на силовые элементы, их ресурс незаметно глазу сокращается. В целом увеличивается количество ремонтов и издержки, прибыльность падает. И это всего лишь от неуловимых, спорадических практически необнаруживаемых ошибок в планировщике и таймингах прерываний. Там завязаны и промахи кэша, и выравнивание по границе памяти, и нагрузка по DMA и т.д. и т.п.
    Вот как с таким бороться?


    1. rukhi7
      03.01.2024 01:47

      Вот как с таким бороться?

      надо диаграммы временные состовлять, как вот здесь, например: Вторая проблема наложения операций во времени

      и по этим диаграммам работать.

      Если вы в коде не сможете гарантированно разнести события во времени и/или правильно обработать их наложение, то все пропало.


      1. Indemsys
        03.01.2024 01:47

        Вы смогли так просто проанализировать и построить диаграммы поскольку отказались от множества дополнительных сервисов и отладочных протоколов.
        С другой стороны реализуй вы SIL, быстрый канал связи по USB или Wi-Fi, много логических отладочных каналов и протоколов, кто знает, может вы свой алгоритм определения давления многократно бы улучшили.


        1. rukhi7
          03.01.2024 01:47

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

          дело в том что я запускал ЧУЖОЙ алгоритм определения давления, как бы библиотечные функции (хоть и с исходным кодом) которые я должен был вызывать и кормить данными и принимать сигналы управления от них, я не мог его не улучшить, не ухудшить, но должен был обеспечить абсолютную диагностику на заданном железе.

          У меня не Wi-Fi у меня Ethernet есть в другой железке в SPI через DMA, собираюсь, кстати написать про высокоскоростной SPI через DMA скоро.


  1. gev
    03.01.2024 01:47
    -1

    После того как стали использовать Haskell, забыли про отладку, как страшный сон! Уже больше года не касался дебагера!


  1. Rusrst
    03.01.2024 01:47
    +1

    А код для МК можно покрыть модульными тестами? Там же куча всего на регистры завязано. Если только подменять значения, но в аппаратной реализации могут быть нюансы. Я вот помню ловил проблему зависания если не проинициализировать целиком таблицу прерываний (т.е. инициализировать только те что используются, остальные не трогать вообще), как такое тестировать? Код то верный.


    1. rsashka
      03.01.2024 01:47
      +2

      Можно покрыть основную логику, когда в место железа подсовываешь Mock-объект


    1. Indemsys
      03.01.2024 01:47
      +3

      Модульные тесты - это если программированием занимается команда. Но если дивайс программируешь в одиночестве, то модульные тесты это как разговор сам с собой.

      В embedded есть устоявшийся путь к упрощению отладки - аппаратный отладочный движок внедрённый в архитектуру процессора. Для программистов под PC такая штука недоступна, а в микроконтроллерах это прогрессирующая фича.
      Современные трассировщики типа J-Trace позволяют быстро ловить плохо воспроизводимые баги.
      Например был у меня вот такой случай. Сброс вочдога:

      /*------------------------------------------------------------------------------
        Сброс таймера WDT
       ------------------------------------------------------------------------------*/
      void WatchDog_refresh(void)
      {
        WDOG_MemMapPtr WDOG = WDOG_BASE_PTR;
        __disable_interrupt();
        WDOG->REFRESH = 0xA602;
        WDOG->REFRESH = 0xB480;
        __enable_interrupt();
      }
      

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

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


      1. rsashka
        03.01.2024 01:47
        +1

        Модульные тесты - это если программированием занимается команда. Но если дивайс программируешь в одиночестве, то модульные тесты это как разговор сам с собой.

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

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


        1. Indemsys
          03.01.2024 01:47
          +1

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

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

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


          1. rsashka
            03.01.2024 01:47
            +2

            Скорее всего разговор упрётся в понимание терминов

            Скорее всего. Наверно подобные тесты можно назвать и интреграционными. Хотя как по мне, модуль может содержать в себе другие модули, а так же логику их взаимодействия. Тогда как моя основная цель тестирования - разделить проверку работы бизнес логики от железа, а название, это дело десятое.


            1. Indemsys
              03.01.2024 01:47

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


              1. rsashka
                03.01.2024 01:47

                Наверно у нас опять будет конфликт формулировок.

                Для меня бизнес-логика в контексте разработки программно-аппаратных комплексов, это те функции, которые определяются изначальными требованиями к разрабатываемому устройству. Их конечно можно моделировать и в Matlab`е, но как потом из этих диаграмм получить реальный код на С++?

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


                1. Indemsys
                  03.01.2024 01:47
                  +2

                  Я вижу софт для встраиваемых систем как многоуровневую систему.
                  Непосредственно с аппаратной частью работает BSP, через BSP с периферией работает middleware и RTOS. И уже всё это пользует прикладной софт. И каждый из этих уровней отлаживается по своему.

                  BSP отладить нормально можно только через SWD/JTAG/Tracing

                  RTOS отлаживается через плагины к IDE и встроенные софтовые отладочные средства. Middleware обычно стороннее и иногда имеет даже свои модульные тесты, например файловые системы.

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

                  В Matlab Stateflow исключительно удобная визуальная симуляция. Симуляция проводится в тестовом окружении. Тестовое окружение - это те же диаграммы, но имитирующие некоторые аспекты внешнего мира. При этом не надо думать о качественном написании интерфейсов функций для облегчения тестирования и прочих атрибутах исходников. Вообще исходники смотреть не надо. Тестовое окружение отключается и диаграммы без изменений могут работать в режиме Software-in-the-Loop (SIL) . SIL в среде Matlab- наиболее мощный метод отладки для прикладного уровня из виденных мной в реальной жизни.


                  1. rsashka
                    03.01.2024 01:47
                    +2

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

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


                    1. Indemsys
                      03.01.2024 01:47
                      +2

                      А оставшееся меньшинство проблем занимают большинство времени  (по правилу Парето). Шучу.


                      1. rsashka
                        03.01.2024 01:47
                        +2

                        А оставшееся меньшинство проблем занимают большинство времени (по правилу Парето). Шучу.

                        Вы не поверите!

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

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

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

                        Поэтому, да, Подписываюсь под словами, что оставшееся меньшинство проблем занимают большинство времени (по правилу Парето). Не шучу. :-)


                  1. aabzel Автор
                    03.01.2024 01:47
                    +1

                    BSP отладить нормально можно только через SWD/JTAG/Tracing

                    RTOS отлаживается через плагины к IDE и встроенные софтовые отладочные средства. Middleware обычно стороннее и иногда имеет даже свои модульные тесты, например файловые системы.

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

                    У меня в прошивке есть UART-CLI и я всё (MCAL,RTOS,App) полноценно могу отладить одной только UART-CLI.
                    https://habr.com/ru/articles/694408/


                    1. Indemsys
                      03.01.2024 01:47
                      +1

                      Речь не о принципиальной возможности что-то отладить, а о времени требуемом на отладку. Т.е. эффективности работы.
                      Как один из минусов отладочного UART-а и протоколов поверх него есть то, что называется "местный эффект", т.е. изменение поведения софта вследствие своего присутствия.
                      Я для старта от голого железа UART-ы давно не применяю, только RTT. RTT тоже что-то требует, но его можно оставить и заморозить местный эффект если он появился.
                      UART сильно тормозит отлаживаемые процессы, а если делать его асинхронным, то будет вмешиваться в тайминги прерываний. Интерфейс RTT прерываний не требует.
                      А отладку по SIL выполняю через толстые каналы типа USB или Wi-Fi.


      1. aabzel Автор
        03.01.2024 01:47

        Модульные тесты - это если программированием занимается команда. Но если дивайс программируешь в одиночестве, то модульные тесты это как разговор сам с собой.

        Модульные тесты необходимы когда код мигрирует на другое окружение: новое железо или другой компилятор.
        https://www.youtube.com/watch?v=-hM38-JDt8c&t=1653s


    1. aabzel Автор
      03.01.2024 01:47
      +1

      А код для МК можно покрыть модульными тестами? 

      Легко! Вот модульный тест на то, что SPI в самом деле отправляет данные.

      static bool test_spi_write_num(uint8_t num) {
          LOG_INFO(TEST, "%s(): SPI %u", __FUNCTION__,num);
          uint8_t array[2];
          bool res = true;
          SpiHandle_t* Node = SpiGetNode(num);
          EXPECT_NE(NULL, Node);
          uint32_t init_int_cnt = Node->it_cnt;
          memset(array,0xFF,sizeof(array));
          ASSERT_TRUE(spi_api_write(num, array, sizeof(array)));
          EXPECT_GR(init_int_cnt, Node->it_cnt);
          return res;
      }


      1. Indemsys
        03.01.2024 01:47

        Интересный пример сразу раскрывающий сложность.

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

        И вот наличие таких избыточных тестов приводят к увеличению объёма рефакторинга, когда его хочется сделать, т.е. к увеличению рутины. Нет ничего хуже рутины в программировании. Рутина, замыливание внимания - серьёзная причина ошибок.


        1. aabzel Автор
          03.01.2024 01:47
          +1

          Рефакторинг - это редактирование имён, изменение структуры аргументов, изменение состава функций.

          Все понимают рефакторинг по-своему. Для меня рефакторинг - это упрощение проекта.
          Когда код простой, то и ломаться не чему.


          1. Indemsys
            03.01.2024 01:47
            +1

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


        1. aabzel Автор
          03.01.2024 01:47

          Рефакторинг API доступа к периферии.

          API доступа к периферии (MCAL) должен быть унифицированным для всех микроконтроллеров. Тогда код MCAL окажется переносимым например с STM32 на ESP32 или Artery.
          Те же наборы модульных тестов будут работать. Та же документация будет актуальна.


          1. Indemsys
            03.01.2024 01:47
            +1

            Интересно кто создает эту унификацию.
            В тексте вашей функции унификации не видно.
            Такое имя как spi_api_write прям просится под рефакторинг.


            1. aabzel Автор
              03.01.2024 01:47

              Такое имя как spi_api_write прям просится под рефакторинг.

              Хорошо. Как Вы бы назвали такую функцию?


              1. Indemsys
                03.01.2024 01:47
                +2

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

                И вот у них API для SPI выглядит так.

                Это динамическая структура, т.е. инстансу этой структуры вы можете дать какое угодно имя. Имена указателей на функции внутри структуры одинаковы для целого класса периферии (UART, I2C, SDIO, GPIO, ...)

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


            1. aabzel Автор
              03.01.2024 01:47

              Интересно кто создает эту унификацию.

              AUTOSAR


  1. rukhi7
    03.01.2024 01:47
    +1

    Гейзенбаг (плавающая ошибка) - программная ошибка, которая исчезает или меняет свои свойства при попытке её обнаружения.

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

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