Ну а меня проблемы ушли после полного отказа от использования встроенного USB. Тестировал очень просто — USB устройство заземляем, а компьютер нет, в соседнюю розетку включаем генератор помех — пусковое устройство для люминесцентных ламп последовательно с дросселем для них же. Все проблемы с помехозащищенностью проявляются в течение нескольких секунд. Проблемы, кстати, впервые обнаружили реальные пользователи с теми же люминесцентными лампами на потолке.
Вы свои устройства не тестировали особо наверное. Проблема в том, что STM не реализовал корректную обработку ошибок в канале передачи. Помеха по землям приводит к тому, что USB перестает работать. C FTDI такого не происходит или происходит, но на несколько порядков реже. Так что встроенный USB годится только для устройств с питанием от USB.
Есть один подход, когда дочерние классы используют функционал родительского. Тут все просто — не хочешь, не используй. А бывает наоборот — родительский класс — фрэймворк — использует виртуальные методы, реализованные в дочерних. Здесь да, без виртуальных методов не обойтись, если уж такой подход реально нужен.
Вот и я про то, что обычно базовый класс используется для того, чтобы иметь общий код для разных дочерних. А тут он просто для красоты, никакого повторного использования кода не проистекает от его наличия.
Касаемо интерфейса — он вообще нужен для полиморфизма исключительно, а не для того, чтоб не забыть, какие методы реализовывать. Применение полиморфизма в микроконтроллере вряд ли выйдет за рамки печати и стримов.
Под размножением кода я понимаю ситуацию, когда для каждого дочернего класса будет создан отдельный инстанс базового класса со своим кодом для вызовов методов дочернего. Что вполне ожидаемо. Методы то не виртуальные, чтобы звать разные методы, нужен разный код в точке вызова.
Конечно, все настройки доступны. Почитать про них можно тут. Они даже выставлены по умолчанию в -Os (оптимизировать размер кода) и -flto (оптимизация при сборке). Но есть подозрение, что вызовы виртуальных функций через таблицу компилятору не так просто отследить. Собственно, проведенный эксперимент говорит ровно об этом.
Ну и чем плоха неатомарность записи CRC? Если запись завершилась, мы получим корректно сохраненные данные, если нет — мы это обнаружим, CRC то не сойдется в итоге.
Главное, что CRC делает лучше, чем пара счетчиков, — помогает отличить сохраненные данные от неинициализированных. Вероятность случайного совпадения 2-х однобайтных счетчиков 1/256, а вероятность случайного совпадения 2-х байтной контрольной суммы — 1/65536. Что делать если не сошлась? Инициализировать значениями по умолчанию.
STM32 — да, есть где разгуляться