Comments 25
А ещё есть опция --double=32
Статический анализатор, например PC Lint тоже помогает, покрайней мере все неявные преобразования типов он укажет.
Ещё есть такая вещь, как integer promotion, и например, если вы будете работать с int16, то без оптимизации, будут добавляться ассемблерные команды, которые осуществляют integer promotion, например ldrh.
Поэтому надо всегда использовать явные преобразования, которые Компилятор может сделать ещё на этапе компиляции, а не в рантайме.
Ну и статический анализатор в помощь.
Как раз недавно попадалась прошивка с управлением мотором под Cortex-М3, где целый FOC аккуратно реализован с фиксированной точкой, а рядом пустяковый термистор, единственная функция которого - уход в ошибку при превышении фиксированного порога температуры, пересчитали из отсчётов АЦП в градусы "в лоб" по формуле, содежащей логарифм с плавающей точкой, чтобы проверять порог в градусах.
Если термистор обрабатывается раз в секунду и нормально настроены приоритеты, то ничего страшного в этом нет. Вообще кортексы довольно быстрые, и оптимизация требуется только когда скорость обработки начинает доходить до сотни тысяч операций в секунду.
Зачем вообще постоянно опрашивать термистор с фиксой? Железный триггер и сигнал прерывания решают проблему имхо.
У нас до на Cortex-M3, где еще не было FPU, тоже был алгоритм на фиксированной точке, по сложности подобный FOC. Основная проблема это баланс между переполнением и шумами дискретизации. Последние радостно переходят в акустические шумы и мотор начинает трещать. Первые приводят к резким сбоям.
Этот баланс можно было бы легко удерживать, если бы не большие диапазоны параметров системы. Ток от 10 до 3000 мА, напряжение от 2 до 48 В, сопротивление обмоток от 0.1 до 12 Ом. И нужно на фиксированной точке предусмотреть все математические действия, чтобы результат был не слишком большим или маленьким для всех комбинаций величин. Если ток меняется на 3 порядка, то выделяемая на сопротивлении мощность это уже I²R, то есть разброс на 8 порядков.
Передача параметров через троеточие приводит к неявному приведению float в double.
ну и можно(я бы даже сказал нужно) явно указать тип константы 0.5f
ошибка реализации библиотечной функции в arm_math.h из CMSIS, revision: v1.4.4
Справедливости ради, ARM поправил это место:
https://github.com/ARM-software/CMSIS-DSP/blob/80bf36c676570fdc078a34dd72aeeb1ecdf7a8ad/Include/dsp/controller_functions.h#L735-L746
Когда именно, искать лень - ваша CMSIS-DSP была выпущена, если не ошибаюсь, в 2015 году.
half_of_N = 0.5 * N;
Компилятор не заменит это на сдвиг на 1 бит вправо?
Очень жалею, что нет в С встроенного fixed point типа, приходится велосипедить. Обычно нам известно, какая точность нужна, и можно от fp отказаться.
.../// Вообще кортексы довольно быстрые, и оптимизация требуется только когда скорость обработки начинает доходить до сотни тысяч операций в секунду. ///...
.../// Обычно нам известно, какая точность нужна, и можно от fp отказаться. ///...
Именно так,
перед тем как заняться реализацией вычислительного процесса, для управления любым актуатором и измерителем:
нужно задаться требуемой (физической / кинематической) динамикой, которую нужно выдать или получить от актуатора и измерителя;
после чего перевести (конструктивная реализация с экономическими ограничениями, мотор + инвертор и выборка-хранение + АЦП) динамику в набор электрических последовательностей "фазных обмоток итп", наилучшей или выбранной формы + периодичности;
в итоге семейства электрических последовательностей, отражающие динамику, на 95% определяют как сам Алгоритм управления, так и его разрядность (5% фактические глюки конкретного чипа и аналогичное на всех этапах разработки и компиляции от кода до бинерника).
printf и sprintf всегда преобразуют float в double в последней версии IAR.
Раньше в IAR такого не было.
В таре куча разных приколов. Столкнулся с проектом в версии 8. Вот синтетический код с сутью проблемы:
double a = 0.2;
uint32_t b = 6;
double r1 = a * b; //=1.2
double r2 = b * a; //=0
Потому что он оценивал тип по первому множителю.
Хорошо, что в C++ можно использовать одну функцию для разных типов данных. Написал std::abs()
, и знаешь, что будет вызван нужный вариант, будь то int
, float
, или double
.
Было дело, писал загрузчик. Собственный протокол, интерфейс USB, память под загрузчик 16 кб. С оптимизацией Os + LTO в указанную память загрузчик не влезал, не хватало буквально сотни байт. Изучая ассемблерный листинг обнаружил функции 64-битной арифметики. Сюрприз был спрятан в хал-библиотеке stm32f4xx_hal_rcc.c:
uint32_t HAL_RCC_GetSysClockFreq(void)
{
...
pllvco = (uint32_t) ((((uint64_t) oscFreq * ((uint64_t) ((RCC->PLLCFGR & RCC_PLLCFGR_PLLN) >> RCC_PLLCFGR_PLLN_Pos)))) / (uint64_t)pllm);
...
}
Небольшая доработка кода, и вуаля - полкилобайта памяти свободно. В моём случае функция вызывается однократно, просадки производительности нет, но память - ценный ресурс. Поэтому читать и понимать map/asm/lst файлы действительно бывает очень полезно.
Фантомный double в прошивках для ядер Cortex-M*