Странная ошибка в моем Arduino ALU

Я обнаружил странную ошибку в своем Arduino. Этого нет в кодексе. Я думаю, что это аппаратная ошибка в ALU микроконтроллера, возможно, только в клоне. Таким образом, существует переменная feedLimit, и ее значение равно 30, что на самом деле не имеет значения. Когда я печатаю ответ feedLimit301000, используя эту команду Serial.println(feedLimit*30*1000) Ответ, напечатанный на последовательном мониторе, - 30528, что явно неверно. Но когда я печатаю feedLimit*30000, ответ правильный (300000). Ответ должен быть одинаковым в обоих случаях, но это не так. Я приложил код, а также скриншот вывода. Может кто-нибудь, пожалуйста, дайте мне знать, если это ошибка в Arduino или что-то еще? Я никогда не сталкивался с такой ошибкой на аппаратном уровне. Спасибо.

int interval, interval2;

int feedingTime = 3; //Должен кормить один раз каждые 3 минуты
int feedLimit = 30; //Если кормить 10 раз или более в течение промежутка времени кормления, фиш умирает
int dieaffter = 30; //Умрет, если его не кормить в течение 10 минут
int feedCounter = 0; //Подсчитывает, сколько раз кормят в промежутке времени кормления

void setup() {
  Serial.begin(9600);
  pinMode(btnPin, INPUT_PULLUP);
  pinMode(ledPin, OUTPUT);

  Serial.println(feedLimit);
  Serial.println(feedLimit*60*1000);
  Serial.println(feedLimit*60000);
}

, 👍0

Обсуждение

Это не жук. Вы работаете с целыми числами на 8 - битном микроконтроллере. Максимум, что они могут вместить, - это 32767., @Majenko


1 ответ


Лучший ответ:

3

Это не ошибка. Просто вы работаете со знаковыми 16-битными целыми числами на 8-битном микроконтроллере. Максимум, что может хранить int, - 32767, а литералы по умолчанию имеют 16-битный знак.

Ваши три отпечатка:

  Serial.println(feedLimit);

Печать 30.

  Serial.println(feedLimit*60*1000);

Умножьте feedLimit на 60 = 1800. Кратно 1800 на 1000 = 1800000. Поместите 1800000 в 16-битный знак = 30538. Также работает по-другому: препроцессор умножает два 16-битных int, чтобы создать новую 16-битную константу int: 60 * 1000 = 60000, которая слишком велика, чтобы поместиться в int, и усекается до 27232. Умножьте на 30 = 30538 при усечении до 16 бит.1

  Serial.println(feedLimit*60000);

Умножьте 30 на 60000. 60000 слишком велик для хранения в int, поэтому он неявно повышается до long. 30 * 60000 = 1800000. В результате получается long, потому что в вашем расчете есть неявное long.

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

  • char: от -128 до +127
  • unsigned char / byte: от 0 до 255
  • int: от -32,768 до +32,767
  • unsigned int: от 0 до 65 535
  • длинные позиции: от -2,147,483,648 до 2,147,483,647
  • unsigned long: от 0 до 4,294,967,295

Вы можете заставить литералы быть определенным типом данных, добавив к ним суффикс:

  • L: Длинный
  • UL: unsigned long

(Есть и другие, но они используются реже).

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

  Serial.println(feedLimit*60*1000L);

1: Кажется, avr-gcc недостаточно умен, чтобы сделать это, вместо этого он всегда обрабатывает каждую операцию отдельно, и каждый раз он работает от int-to-int, используя два 8-битных регистра для представления каждого значения или результата.

,

Это было очень полезно. Спасибо :), @Z Dhillon

В качестве альтернативы можно использовать типы с явными размерами: int8_t, int16_t, int32_t и т. Д. Они будут иметь ожидаемый размер независимо от того, какой процессор используется., @PMF

Я не думаю, что свернутая константа 60000 усекается до 27232. Вы проверили это в скомпилированном коде?, @the busybee

@thebusybee Кажется, avr-gcc недостаточно умен, чтобы сложить 60 * 1000 как константы в одну константу 60000. Вместо этого он выполняет отдельные вызовы " mul "во время выполнения - "<variable>* 60", за которым следует "<result of previous>* 1000". И на каждом этапе он работает всего с 2 8-битными регистрами на значение (или просто 1 в случае 60, конечно, это не так глупо), и все результаты каждый раз попадают в два регистра. Все усекается и снова усекается. Но он не думает объединять литералы таким образом..., @Majenko

Re “Кажется, avr-gcc недостаточно умен, чтобы сложить 60 * 1000 как константы в одну константу 60000”: Он не может этого сделать: это нарушило бы правила языка о неявной типизации. Но это умно: он замечает, что "feedLimit" не меняется, и складывает " feedLimit*60*1000` в 30528. Никаких вызовов " mul " во время выполнения., @Edgar Bonet

@EdgarBonet Не в соответствии с выводом "avr-gcc-S" в моих тестах. Там был длинный список инструкций "мул", чтобы получить результат., @Majenko

Ммм... странно. Avr-g++ 7.3.0, поставляемый с Arduino 1.8.15, выполняет оптимизацию. Вы компилировали с помощью -Os -flto?, @Edgar Bonet

@EdgarBonet Нет, я этого не делал. Я бы не ожидал, что-flto будет иметь какой-либо эффект, так как нет никакой связи. -Ос *может быть* но кто знает? В любом случае это не имеет значения, поскольку точное "почему" не имеет значения. Что бы компилятор ни решил сделать или не решил сделать, это все равно 16-битные целые числа, если только нет веской причины для его продвижения в long., @Majenko

@Majenko вчера вечером у меня тоже были проблемы с попыткой воспроизвести то, что вы видели в folding. Но мои мысли по этому поводу были больше о том, что независимо от того, что он делает, вы просите его сделать что-то неопределенное, и в таких случаях трудно ожидать оптимизатора. Я бы проверил -fwrapv, чтобы проверить наличие изменений. Но ни первоначальной проблемы, ни теста. Как вы сказали, в конечном счете это не имеет значения. **Единственное, что я хотел добавить, это то, что указанная база литерала влияет на то, считаются ли кандидатами типы unsigned**. Например, 32768-это долго, но 0x8000 - это unsigned int с 16-битными системами., @timemage