Странное поведение от uint32_t, действуя как подписанный int (Nano clone)

arduino-nano printf

У меня проблемы с этими переменными. Как вы можете видеть, они имеют тип uint32_t, поэтому они должны иметь максимальное значение 4 миллиона или около того, но они переворачиваются и становятся отрицательными на 32 767, как подписанный int. Вот соответствующий код:

byte lPin = 7;
char buff[100];
uint32_t loAvg = 0;
uint32_t hiAvg = 0;

void setup() {
  pinMode(lPin, OUTPUT);
  digitalWrite(lPin, HIGH); // Active LOW, so this is off

  Serial.begin(9600);
  // енерировать высокие/низкие средние
  for(int i = 0; i < 100; i++) {
    loAvg += analogRead(A0);
  }
  loAvg /= 100;

  digitalWrite(lPin, LOW);
  delay(500);

  for(int i = 0; i < 100; i++) {
    hiAvg += analogRead(A0);
  }
  hiAvg /= 100;
  
  digitalWrite(lPin, HIGH);
  delay(500);
  
  sprintf(buff, "loAvg=%d, hiAvg=%d", loAvg, hiAvg);
  Serial.println(buff);

  Serial.println(hiAvg);
  digitalWrite(lPin, LOW);
  hiAvg = 0;
}

void loop() {
  sprintf(buff, "test=%d", hiAvg);
  Serial.println(buff);
  hiAvg+=100;
}

Я удалил кучу вещей из кода, чтобы не публиковать все это (гораздо дольше). Хотя, если это окажется релевантным, я могу это сделать, просто думаю, что сузил проблему до этого. Наблюдая за последовательным монитором, я получаю следующее при запуске:

loAvg=287, hiAvg=0
933
test=0
test=100
test=200
...

Тест=# повторяется до 32700, затем продолжается при -32736. Меня смущает hiAvg=0, за которым следует 933, показывая, что он полностью получает высокое среднее значение, потому что это примерно то, что должно быть. Даже если это sprintf() возится с ним, в функции setup значение должно быть только в диапазоне 900, чтобы оно не переполнялось там. Есть какие-нибудь идеи?

, 👍0

Обсуждение

речь идет только о трех линиях ... моя ставка-на sprintf() ... а именно на %d ... это форматирует данные для вывода, @jsotola

"%" PRIu32 Почти нет причин использовать "sprintf" над " snprintf`. В этом коде, как написано, нет смысла использовать ни то, ни другое., @timemage

Этот код выше-это только те части, которые имели отношение к вопросу, поэтому я могу понять, что не вижу смысла. "sprintf` используется только во время тестирования. Когда ОТЛАДКА определена, последовательные отпечатки находятся между блоками #ifdef / #endif, поэтому они не присутствуют во время нормальной работы. В конце концов я понял, что мне нужен %lu. Спасибо вам за ответы и ответы., @HaLo2FrEeEk


2 ответа


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

4

%d ожидает подписанный int. %u ожидает unsigned int. Вставьте модификатор l, чтобы ожидать long в каждом из этих случаев. Таким образом, вам нужен код формата %lu для печати uint32_t.

Следующий код:

#include <Arduino.h>

void setup()
{
    uint32_t x=110000;
    char buf[50];

   // Open console
   Serial.begin(115200);
   
    sprintf(buf, "%%d: %d\n", x);       Serial.print(buf);
    sprintf(buf, "%%u: %u\n", x);       Serial.print(buf);
    sprintf(buf, "%%ld: %ld\n", x); Serial.print(buf);
    sprintf(buf, "%%lu: %lu\n", x); Serial.print(buf);
}


void loop() {
   ;
}

печатает это:

%d: -21072
%u: 44464
%ld: 110000
%lu: 110000
,

2

*printf и заданные типы разрядности

#include <inttypes.h><inttypes.h> или C++#include <cinttypes><cinttypes> (когда у вас есть; не на основе AVR ардуино) предоставляет доступ к макросам, определенным для спецификаторов формата для каждой из фиксированных, быстрых и наименьших версий этих типов. Все эти макросы расширяются до некоторого строкового литерала или другого.

Для uint32_t макрос, определяющий его спецификатор формата, является PRIu32. Для AVR это "%lu". Это не должно быть "%lu" в общем случае. uint32_t-это один из тех типов, где вам часто может сойти с рук использование "%lu", и он либо работает точно правильно для платформы, либо несколько неправильно, но все равно работает, потому что это платформа, где unsigned int и unsigned long имеют одинаковое представление.

В C и C++, а также в Arduino смежные строковые литералы объединяются в одну логическую строку на ранних этапах компиляции, следующих за препроцессором макросов. Итак, намерение состоит в том, чтобы вы выбрали правильный макрос и поместили его после чего-то вроде "%" или "%12", если вам нужна минимальная ширина 12. Как в "%" PRIu32 и "%12" PRIu32 соответственно. Итак, в вашем случае, включив (прямо или косвенно) <stdint.h><stdint.h> для uint32_t и <inttypes.h><inttypes.h> для PRIu32 или их аналогов на C++ заголовки, ваш код будет выглядеть примерно так:

snprintf(
  buf,
  sizeof buf,
  "loAvg=%" PRIu32 ", hiAvg=%" PRIu32,
  loAvg,
  hiAvg
);

Нет никаких причин не использовать snprintf. Причина, по которой не-n версия вообще существует, в основном заключается в том, что snprintf появился в 1999 году, а не примерно в середине 1970-х годов. Я бы сказал, что сейчас довольно безопасно использовать snprintf. Там, вероятно, не было Arduino, который не поддерживает его. Примерно единственная причина использовать sprintf-это если вы вызываете его с нулевым указателем для аргумента buffer, чтобы заставить его выполнить сухой прогон и сообщить, сколько памяти вам нужно для результата, и вы просто не хотите указывать предельный размер.

Есть PRI... макросы для всех комбинаций стандартной разрядности и выбора знаковой десятичной, беззнаковой десятичной, строчной шестнадцатеричной, заглавной шестнадцатеричной и т. Д. Например, " % ""PRId16" - для int16_t в десятичной системе счисления, "%" PRIX64-для uint64_t в шестнадцатеричной системе счисления с прописными буквами. Это те, которые предназначены для быстрых и наименее битовых типов, и все шоу повторяется с макросами, начинающимися с SCN для спецификаторов scanf, а не для того, чтобы вы действительно использовали функции семейства scanf.

snprintf и sprintf являются вариадическими функциями. После спецификатора format компилятору не доступна информация о типе, чтобы знать, какие преобразования применять. Он преобразует типы меньшего ранга , чем int, до int на unsigned int, но это все. Например , он не будет надежно преобразовывать unsigned long long в unsigned int, как это было бы, если бы типы были известны. Современные компиляторы иногда предупреждают вас, если вы грубо неправильно применяете спецификаторы формата, но они не обязаны это делать. Они не должны расчленять строку формата и пытаться восстановить информацию о типе. Таким образом, вы должны убедиться, что все, что вы передаете в качестве аргументов, совпадает со спецификаторами формата. Как вы уже видели, это сбивает с толку, если вы ошибаетесь с функциями типа printf, но это не вызывает реальных проблем, если вы, знаете ли, не ограничиваете его пребывание в выходном буфере версией snprintf. В случае передачи unsigned long long, где на практике ожидалось unsigned int для малоконечной системы, такой как AVR, может показаться, что это делается для одного аргумента. Но тогда он может потерпеть неудачу при следующем аргументе, потому что размер последнего аргумента был правильным, а теперь он читает не с того места.

Печать/Поток/Последовательная конкатенация и буферизация

То, как вы используете sprintf в своем коде, не использует ни одной из его функций, которые не используются Serial / Print.

Serial.print(F("loAvg="));
Serial.print(loAvg);
Serial.print(F(", hiAvg="));
Serial.println(hiAvg);

Несмотря на то, что он занимает четыре строки, это на самом деле меньше, быстрее, надежнее кода, чем sprintf one-liner.

Если вы собираетесь поместить вывод в буфер первым, это может иметь смысл, например, для UDP или nRF24L01. Но, Print/Stream/Serial уже объединяет и буферизует вещи для вас.


Кстати, если вы собираетесь использовать функции printf и делаете это на основе AVR Arduino, посмотрите на документацию avr-libc для вариантов _P, которые берут свои строки формата из PROGMEM и макроса PSTR.

Также можно завершить реализацию <stdio.h><stdio.h> для stdout и stderr с помощью специфичных для libc средств. Таким образом, если вы действительно хотите, вы можете иметь обычную функцию printf, выталкивающую данные через последовательный (среди прочего) так, что вам не нужно повторно буферизировать с sprintf.

,