предотвратить арифметическое переполнение

Я хочу рассчитать напряжение с помощью периферийного АЦП PIC18F14K50. Результат находится в диапазоне от 0 до 1023 (10 бит). Итак, я использовал этот простой расчет:

uint16_t voltage = ADC_Result * 5000 / 1023;

Однако результаты неверны. Я предполагаю, что произошло арифметическое переполнение. Я пробовал много сочетаний скобок, изменения порядка элементов и т
. Д. Лучший результат был 4088, когдаADC_Resultбыло 1023 с использованием кода ниже; что действительно далеко от 5000.

uint16_t voltage = ADC_Result * (5000 / 1023);

Что мне делать, чтобы получить лучшие результаты в приведенном выше расчете? Пожалуйста, не предлагайте числа с плавающей запятой, так как они вызывают сбой в MCU! Они используют много ресурсов без какой-либо реальной выгоды.

3 ответа

Решение

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

uint16_t voltage = (uint32_t)ADC_Result * 5000 / 1023;

РЕДАКТИРОВАТЬ

Если деление на 1023 слишком медленное, вы можете получить примерно равное преобразование, изменив 5000 / 1023 на 5005 / 1024, что может использовать быстрый битовый сдвиг для деления:

uint16_t voltage = (uint32_t)ADC_Result * 5005 >> 10;

NB 1023 * 5005 / 1024 ≃ 5000.1123

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

В твоем случае, 1023 * 5000 == 3192 (потому что реальный результат 5115000 не подходит), так что это неверно. 5000 / 1023 == 4, который является ожидаемым результатом целочисленного деления. РазделениеADC_Result к 1023 приведет к тому же поведению.

Вы можете рассчитать это в uint32_t а затем проверьте, подходит ли он uint16_t:

uint32_t result_tmp = ADC_Result * (5000 / 1023);
uint16_t result;

if (result > 0xffff) {
    // this won't fit
} else {
    result = (uint16_t) result_tmp;
}

Что мне делать, чтобы получить лучший результат в приведенном выше расчете?

Переполнение кода ОП 1`6-битная математика.


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

// uint16_t voltage = ADC_Result * 5000 / 1023;
uint16_t voltage = (ADC_Result * 5000LU + 1024u/2) / 1024u;
// or
#include <stdint.h>
...
uint16_t voltage = (ADC_Result * UINT32_C(5000) + 1024u/2) / 1024u;

В L в 5000LU предоставляет как минимум 32-битную математику.

Использовать U для потенциально более простой / быстрой математики и более простого округления ADC_Result не отрицательный.

+ 1024/2 выполняет округление до ближайшего, а не усечение.

Используйте 1024 вместо 1023 для правильного масштабирования с учетом обычных характеристик аналого-цифровых преобразователей. Дополнительное преимущество: более быстрое деление, поскольку 1024 - это степень двойки.

Другие вопросы по тегам