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

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

Держите его легким

Во-первых, чтобы отлавливать ошибки в продакшене, я хотел записывать данные об использовании. Регистрация любого типа данных на встроенном устройстве, очевидно, сложнее, чем на персональном компьютере, поскольку на целевом устройстве не так много места для хранения всей программы. Кроме того, пропускная способность связи, необходимая для быстрой передачи всей информации, в конечном итоге ограничивается, например, при использовании Bluetooth Low Energy. Итак, представьте себе, что вам нужно хранить множество удобочитаемых предложений в прошивке для отправки по беспроводной сети удаленному узлу: это слишком тяжело.

Имея это в виду, я реализовал легкое решение: каждый журнал должен содержать максимум 18 байтов (BLE 4.0 имеет ограничение в 20 используемых байтов в каждом пакете). Во-первых, код ошибки использует один байт. Затем удобочитаемая строка дает больше информации об ошибке: происхождение, причины или что-то еще, что вы ожидаете, чтобы понять проблему. Например, вот несколько кодов ошибок, связанных с некоторыми проблемами с хранилищем:

#define WARN_STORAGE_CORRUPT        (WARN_STORAGE + 0)
#define WARN_STORAGE_LOW            (WARN_STORAGE + 1)
#define WARN_STORAGE_FAILURE        (WARN_STORAGE + 2)

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

Все предупреждения отправляются на пульт по мере поступления через Bluetooth или ставятся в очередь для последующей передачи, если он не подключен. Затем приложение Equisense отправляет эти данные в нашу базу данных вместе с адресом электронной почты пользователя, моделью телефона, версией прошивки и т. д. для дальнейшего анализа… (продолжайте читать 😉)

Лучший вариант использования: перехват ошибочных утверждений

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

Мой код заполнен утверждениями, которые проверяются тут и там (надеюсь, и ваши тоже). Когда утверждение терпит неудачу во время работы в отладочной конфигурации, у меня есть обработчик, который может записывать файл, строку и код ошибки в последовательный вывод или RTT (см. Segger). Вот пример из Flash-драйвера, который не умеет записывать куски больше FLASH_PAGE_SIZE (512), строка 123:

#define FLASH_PAGE_SIZE 256
void flash_write_page(uint32_t addr, uint8_t* data, uint32_t length)
{
    APP_ERROR_CHECK_BOOL(length <= FLASH_PAGE_SIZE);
    [...]
}

Если я вызову flash_write_page с длиной более 512 байт, последовательный журнал напечатает строку ниже и сбросит:

code: 0x0, line: 123, file: src/flash.c

Что очень полезно при отладке.

Очевидно, я хотел иметь такую ​​же функцию в выпущенных прошивках. Хранить и отправлять полное имя файла в 18 доступных байтах было слишком тяжело, поэтому я решил иметь хеш-таблицу, хранящую связь между именем исходного файла и хэшем длиной 4 байта, сгенерированным во время компиляции и пригодным для использования в предупреждении. сообщение. Для каждой единицы компиляции (файл .o) я генерирую новый хэш, который можно скомпилировать в единицу, а хэш добавляется в файл CSV FILENAME_HASTABLE_OUTPUT. Вот интересная часть Makefile:

FILE_CHKSUM = $(word 1,$(shell echo $(1) | cksum))
FILE_CHKSUM_HEX = $(shell echo "obase=16; $(call FILE_CHKSUM,$(1))" | bc)
# $1 command
# $2 flags
# $3 message
define run
$(info $(call PROGRESS,$(3) file: $(notdir $($@)))) \
$(NO_ECHO)$(1) -MP -MD -c -o $@ $(call get_path,$($@)) $(2) $(INC_PATHS) -DFILE_CHKSUM='((uint32_t) 0x$(call FILE_CHKSUM_HEX,$@))'
endef

# Create object files from C source files
# Write filename checksums in a file if it doesn't exist
%.c.o:
   $(call run,$(CC) -std=c99,$(CFLAGS),Compiling)
   @grep -s -q -F "0x$(call FILE_CHKSUM_HEX,$@) = $@" ${FILENAME_HASHTABLE_OUTPUT} || echo "0x$(call FILE_CHKSUM_HEX,$@),$@" >> ${FILENAME_HASHTABLE_OUTPUT}

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

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

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

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

Следующим шагом стала настройка информационной панели, позволяющей отслеживать качество кода с течением времени. Теперь я могу каждый день проверять, какие ошибки произошли чаще всего, а также количество затронутых людей, работающие на определенной версии прошивки и т. д. К вашему сведению, все предупреждения отправляются в Big Query, а затем связываются с Data Studio. Эти инструменты очень удобны и полностью удовлетворяют мои потребности. Я могу делиться своими информационными панелями с коллегами по работе и легче отслеживать ошибки пользователей, добавляя фильтры и отображая красивые графики 🤩.

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

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

Даже если интеграционные тесты имеют преимущество в обнаружении ошибок до выпуска рабочей версии, мне было бы намного сложнее реализовать их самостоятельно, и, наконец, теперь я хорошо понимаю сбои, возникающие на наших трекерах. Сегодня я чувствую, что эти простые шаги обеспечения качества обеспечивают наибольшую отдачу от инвестиций. На сегодняшний день я не нашел простого решения для реализации интеграционных тестов на основе команд Bluetooth. Есть такие инструменты, как автоматизированные тесты для nRF-Connect, например, но я думаю, что лучше всего будет использовать наше мобильное приложение для записи тренировок в цикле.

Пожалуйста, расскажите всем, как вы справляетесь с контролем качества прошивки в своих проектах ☺.