Sprintf unexpected result

Hi,

I'm trying to assemble a string using sprintf(), and for some reason I don't get the expected result. At the moment, I still have no idea why but I probably got some formatting wrong.

char reportMessage[36];

long azimuthFeedback = 12345;
long azReady = 6;
long azAlarm = 7;

long elevationFeedback = 8901;
long elReady = 8;
long elAlarm=9;

void setup() {
  Serial.begin(9600);
}

void loop() {

  sprintf(reportMessage,"reportToMCU(%05lu,%1i,%1i,%04lu,%1i,%1i)\r\n",azimuthFeedback,azReady,azAlarm,elevationFeedback,elReady,elAlarm); 
  Serial.print(reportMessage);

  
delay(1000);
}

expected output: reportToMCU(12345,6,7,8901,8,9)
actual output: reportToMCU(12345,6,0,0007,8901,0)

For some reason there is an extra zero inserted after azReady (6), which makes everything after it move one place - shifting the last '9' out.

I feel like I'm missing something very obvious.. Any help would be greatly appreciated!

Thanks,
Jens

I got the expected output. Have you tried just using %l for all your format specified? Why use %lu, and what is %i?

#include <stdio.h>


char reportMessage[36];

long azimuthFeedback = 12345;
long azReady = 6;
long azAlarm = 7;

long elevationFeedback = 8901;
long elReady = 8;
long elAlarm=9;

void setup() {}

int main() {

  sprintf(reportMessage,"reportToMCU(%05lu,%1i,%1i,%04lu,%1i,%1i)\r\n",azimuthFeedback,azReady,azAlarm,elevationFeedback,elReady,elAlarm); 
  printf("]%s[\n",reportMessage);

  

}

Output:

]reportToMCU(12345,6,7,8901,8,9)

[

That's your program adjusted for an online C interpreter… and the output it produced.

a7

Huh, strange. I'm currently trying to work out why I wrote it the way I did - it has been some time ago and I worked it out then with some tutorial. I probably had the variables in another type then. I remember changing to long because of some incompatibility issue between an Atmega328 and an ESP32.

Currently I'm testing just on a 'clean' Arduino Uno.

All the variables you are giving to sprintf() are long, so you should use the "l" specifier for each one. Not "lu" because that is for unsigned long. Not "i" because that is for a (regular size) int. Depending on the type of arduino you are using, int and long may be the same, but given the problem you describe, int and long are different in the arduino you are using.

The sprintf() is not always compatible between boards with 16 bits integers and boards with 32 bits integers.

The weird thing about sprintf() is that parameters with types as 'uint32_t' and 'int16_t' may not be used. The sprintf() function can only match with 'int', 'long', and so on.

The description of the format is here: https://cplusplus.com/reference/cstdio/printf/

If you use 'long', then you have to match that in the format string with "ld".
With five "%ld", it works:

char reportMessage[80];

long azimuthFeedback = 12345;
long azReady = 6;
long azAlarm = 7;

long elevationFeedback = 8901;
long elReady = 8;
long elAlarm = 9;

void setup() {
  Serial.begin(9600);
}

void loop() {
  sprintf(reportMessage, "reportToMCU(%ld,%ld,%ld,%ld,%ld,%ld)\r\n", azimuthFeedback, azReady, azAlarm, elevationFeedback, elReady, elAlarm);
  Serial.print(reportMessage);

  delay(1000);
}

Test it with a Uno in the Wokwi simulator:

By the way, if you add a "size", then that is the minimum size.

the statement

  sprintf(reportMessage,"reportToMCU(%05lu,%1i,%1i,%04lu,%1i,%1i)\r\n",azimuthFeedback,azReady,azAlarm,elevationFeedback,elReady,elAlarm); 

is using %1i it should be %li, e.g.

  sprintf(reportMessage,"reportToMCU(%05lu,%li,%li,%04lu,%1i,%1i)\r\n",azimuthFeedback,azReady,azAlarm,elevationFeedback,elReady,elAlarm); 

or did you mean %1li

Thanks all!

Indeed, it was a problem changing variable types between an ESP32 and something for an Arduino Uno. Long is 64bit on the ESP32, and 32bit on the Arduino Uno.

So this is the working code on the arduino

sprintf(reportMessage,"reportToMCU(%05ld,%1ld,%1ld,%04ld,%1ld,%1ld)\r\n",azimuthFeedback,azReady,azAlarm,elevationFeedback,elReady,elAlarm); 

Thanks a lot, y'all!

Can someone explain to me what "%1ld" does ?

[UPDATE] I figured it out. It is nothing. I call it a bug.

On ESP32, int and long are the same, 32-bit. So on ESP32 you could get away with mixing up "i" and "l" and you might therefore believe they are interchangeable on any board. But in Uno and other AVR based Arduino, int is 16-bit and long is 32-bit. So you can't get away with mixing up "i" and "l", you have to get them right.

1 Like

Nope

ok, thanks! All clear now!

How did you determine that information?
To me, u is an absolutely legal format specifier.

https://www.nongnu.org/avr-libc/user-manual/group__avr__stdio.html#gaa3b98c0d17b35642c0f3e4649092b9f1

Note: It is "padded on the left with zeroes" only if the precision specifier has a leading zero. The format "%5X" will display 15 as " F" while "%05X" will display it as "0000F".

They can be used, but it is a bit inconvenient.
<inttypes.h> defines a bunch of macros that give the proper format.

#include <stdio.h>
#include <stdint.h>
#include <inttypes.h>

int main(void)
{
  uint16_t a = 175;
  int32_t b = -987654;
  int_fast8_t c = -42;

  printf("a = 0x%04" PRIX16 "\n"
         "b = %" PRId32 "\n"
         "c = %" PRIdFAST8 " = %#" PRIoFAST8 "\n",
         a, b, c, c);
  return 0;
}

Expanded form for Windows 10/Cygwin

  printf("a = 0x%04" "h" "X" "\n"
         "b = %" "d" "\n"
         "c = %" "hh" "d" " = %#" "hh" "o" "\n",
         a, b, c, c);

for ESP32

  printf("a = 0x%04" "h" "X" "\n"
         "b = %" "d" "\n"
         "c = %" "d" " = %#" "o" "\n",
         a, b, c, c);

and for AVR

  printf("a = 0x%04" "X" "\n"
         "b = %" "ld" "\n"
         "c = %" "d" " = %#" "o" "\n",
         a, b, c, c);

Output on Cygwin

a = 0x00AF
b = -987654
c = -42 = 0326

https://en.cppreference.com/w/c/types/integer#Format_macro_constants

1 Like

When using sprintf(), the format specification is at compiler level. The "%d" is a "int", the "%ld" is a "long int", and so on. That is regardless of how many bytes the 'int' actually is.

This is not portable, and therefor wrong in my opinion:

uint32_t x = 123;
uint16_t y = 8;
sprintf(buffer, "Hello %d %d", x, y);   // might work, might not work

Do you know my rant about the "round()" macro ? https://github.com/arduino/ArduinoCore-API/issues/76
This is the same thing, you are not supposed to write code that is inherently wrong.

oqibidipo, can the Arduino Uno use the Macro Constants ? I have never used those. I'm afraid that I get lost, so I rather convert the variable in code before the sprintf().

By the way, I learned from this forum that a parameter for sprintf() is promoted to 'int' when they are smaller. So there is already some promoting going on, even without the Macro Constants.

Yes, although avr-libc does not support the h modifier and 8-bit values are printed like 16-bit values:

a = 0x00AF
b = -987654
c = -42 = 0177726
1 Like

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.