Можно ли написать функцию округления constexpr для AVR? - PullRequest
0 голосов
/ 27 марта 2019

Я пишу класс для настройки последовательного порта на микроконтроллере AVR.У меня есть шаблонная функция, которая принимает в качестве параметров значение тактовой частоты процессора и желаемую скорость передачи данных, выполняет быстрый расчет, проверяет, находится ли фактическое значение в пределах 1,5% от требуемого значения с помощью статических подтверждений, а затем возвращает фактическое значение для установки внутри.8-битный регистр.Мне нужно использовать std :: round, и его возвращаемое значение должно быть constexpr, чтобы все оценивалось во время компиляции.Это проблемный бит:

#include <cmath>

template<int c, int b>
constexpr int UBRRValue() {
   // return the value for UBRR register to get close to the desired baud rate
   // avoid integer division
    return std::round( static_cast<float>(c) / ( 16 * b ) - 1 );
}

int main() {
    constexpr auto val = UBRRValue<2000,25>();
    return val;
}

Это прекрасно работает для x86 в проводнике компилятора, он возвращает 4. На AVR нет cmath, float round (float) определен в math.h и реализован в сборке, так что, вероятно, не constexpr.После быстрого поиска я нашел это: https://stackoverflow.com/a/24348037/11221049 Я сделал несколько настроек, чтобы затем gcc указал, что эта функция не является constexpr.Я сделал это constexpr, но тогда его результат никогда не будет constexpr, потому что он требует доступа к члену объединения, который не был инициализирован.Союз трюки не являются constexpr.Итак ... возможно ли сделать функцию constexpr round (зная, что что-то из math.h написано непосредственно в ассемблере)?Как это делается в gnu libc ++?

Ответы [ 2 ]

2 голосов
/ 27 марта 2019

То, что вы пытаетесь вычислить, является правильно округленным результатом (c / (16 * b)) - 1.Вы применяете float, чтобы избежать целочисленного деления, но это почти бессмысленно, если вы все равно собираетесь округлять.

Обратите внимание, что мы можем безопасно переместить -1 за пределы округления (изменит только результатесли вы отбрасываете -1 из-за отсутствия точности с плавающей точкой, что, по-видимому, вам не нужно).Таким образом, все, что нам нужно, это правильно округленный результат c / (16*b).Если мы сделаем это как целочисленное деление, мы получим округленный результат.Мы можем получить средне-округленный результат, просто добавив половину делителя к дивиденду (при условии, что оба положительны):

template<int c, int b>
constexpr int UBRRValue() {
   // return the value for UBRR register to get close to the desired baud rate
    return (c + 8*b) / (16 * b) - 1;
}

Вот несколько тестов, которые он проходит: https://godbolt.org/z/Va6qDT

0 голосов
/ 27 марта 2019

Округление значений с плавающей запятой всегда можно сделать, просто сложив или вычтя 0,5, а затем приведя обратно к целому числу. Нет необходимости вызывать что-либо из std :: namespace.

constexpr int round(double x) {
  return (x >= 0.0) ? int(x + 0.5) : int(x - 0.5);
}

constexpr int UBRRValue(int c, int b) {
  return round(static_cast<double>(c) / (16 * b) - 1);
}

int main() {
  constexpr auto val = UBRRValue(2000, 25);
  return val;
}

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

/ редактировать Как упоминалось в комментариях, утверждение, что это «всегда» работает, не соответствует действительности. Однако для этой ситуации достаточно, поскольку регистр скорости передачи данных не будет ни отрицательным, ни 0.

...