sprintf and binary coded decimal

Hi all, a quick sprintf question if you don't mind...

I have hours, minutes and seconds, each held as a byte in bcd format, which have been read from an rtc. I want to use sprintf to format this into hh:mm:ss format, for display on an lcd.

As the values are in bcd, I first tried using "%x:%x:%x" but single digit values were not padded.

Second attempt was "%2x:%2x:%2x" which pads single digit values, but with spaces, not zeroes.

Third attempt was "%#2x:%#2x:%#2x" but this was worse, as sprintf wants to format 32 as 0x32 for example.

Can it be done with sprintf without resorting to bcd to decimal conversion first?

Thanks,

Paul

%02x

Pete

If each value is a separate byte, then it is in effect a hexadecimal value but some of the values (A-F) never used.

If you just display it as hexadecimal then it will appear just as if it were decimal.

The other option is to print each digit separately and use bit shifting to get the two nibbles:

sprintf(myStr, "%d%d", hours >> 4, hours & 0x0F);

If the encoding is more complex, say you have 4 bits of units, 2 bits of tens, and other things in the other 2 bits (12/24hr flag, etc), then bit shifting plus masking, or hexadecimal plus masking, can be used:

sprintf(myStr, "%d%2", (hours >> 4) & 0x03, hours & 0x0F);
- or -
sprintf(myStr, "%x", hours & 0x3F);

el_supremo:
%02x

Works perfectly. Thanks Pete!

Coming back to this, I just noticed something odd. Maybe its too late at night and my brain isn't working...

Here's the sketch:

#include <Wire.h>
#define RTCadrs 0x68

void setup() {
  Wire.begin();
  Serial.begin(38400);
  //setRTCtime(0x00, 0x17, 0x23, 0x02, 0x23, 0x01, 0x14);

}

void loop() {
  
  Wire.beginTransmission(RTCadrs);
  Wire.write(byte(0));
  Wire.endTransmission();
  Wire.requestFrom(RTCadrs, 3);
  char time[12];
  sprintf(time, "%02x:%02x:%02x", Wire.read(), Wire.read(), Wire.read());
  Serial.println(time);
  delay(1000);
  
}

void setRTCtime(byte secs, byte mins, byte hours, byte dow, byte date, byte month, byte year) {
  
  Wire.beginTransmission(RTCadrs);
  Wire.write(byte(0));
  Wire.write(secs);
  Wire.write(mins);
  Wire.write(hours);
  Wire.write(dow);
  Wire.write(date);
  Wire.write(month);
  Wire.write(year);
  Wire.endTransmission();

}

and here's the output:

23:27:45
23:27:46
23:27:47
23:27:48
23:27:49
23:27:50
23:27:51
23:27:52
23:27:53
23:27:54
23:27:55
23:27:56
23:27:57
23:27:58
23:27:59
23:28:00
23:28:01
23:28:02
23:28:03
23:28:04
23:28:05
23:28:06
23:28:07
23:28:08
23:28:09
23:28:10
23:28:11
23:28:12
23:28:13
23:28:14
23:28:15
23:28:16
23:28:17
23:28:18

So the time is being printed in hh:mm:ss format. Great.

But what's wierd is this: the first use of Wire.Read() should give the seconds, the second Wire.Read() should give the minutes and the third Wire.Read() should give the hours. (This is a DS3231 RTC, but its just the same as a DS1307 in this respect.) So what should get printed is ss:mm:hh.

It is as though the sprintf function is formating the values it is given in right-to-left order instead of left-to-right. How does it know to do this?

Welcome to C. While all three function calls are going to be called, the order in whcih they are called is not guaranteed. Different compilers might do things differently.

Welcome to C. While all three function calls are going to be called, the order in whcih they are called is not guaranteed. Different compilers might do things differently.

Where did you get that from.

The order of function calls is always as written.

Looks to me like PaulRB has miss read the spec.

Mark

holmes4:

Welcome to C. While all three function calls are going to be called, the order in whcih they are called is not guaranteed. Different compilers might do things differently.

Where did you get that from.

The order of function calls is always as written.

Looks to me like PaulRB has miss read the spec.

Mark

The order, in linear program line space, is as written. However, the order of function calls when embedded as parameters to another function call, is compiler specific:

  sprintf(time, "%02x:%02x:%02x", Wire.read(), Wire.read(), Wire.read());

What order are the Wire.read() functions called? In linear programming space, that sprintf function is what is executed "in order". The parameters to it, however, may be executed 1-2-3, or 3-2-1. It could even change from one situation to another due to optimisations created by the compiler.

The parameters to it, however, may be executed 1-2-3, or 3-2-1. It could even change from one situation to another due to optimisations created by the compiler.

Not by the compiler but by the function being called! and for sprintf etc thats specified as the order in which they are written. Think about it and try "writing" your own sprintf()

Mark

Thanks majenko and KeithRB. I never thought of that before, but it makes sense. A "gotcha" to remember. So if I assign the results of the 3 Wire.read () calls to variables in 3 statements, then pass those to sprintf () there can be no ambiguity over the order.

holmes4:

The parameters to it, however, may be executed 1-2-3, or 3-2-1. It could even change from one situation to another due to optimisations created by the compiler.

Not by the compiler but by the function being called! and for sprintf etc thats specified as the order in which they are written. Think about it and try "writing" your own sprintf()

Mark

No, because the functions are called first, then the results of those functions are passed to the sprintf function. The sprintf function does NOT call the functions - they are called BEFORE the sprintf() function - the sprintf() function has no control over the calling order - that is purely down to the compiler and how it optimises the code.

PaulRB:
Thanks majenko and KeithRB. I never thought of that before, but it makes sense. A "gotcha" to remember. So if I assign the results of the 3 Wire.read () calls to variables in 3 statements, then pass those to sprintf () there can be no ambiguity over the order.

Absolutely. If there is any chance of ambiguity then you want to nobble it as fast as possible :wink:

Not by the compiler but by the function being called! and for sprintf etc thats specified as the order in which they are written. Think about it and try "writing" your own sprintf()

You are thinking at too high a level. Down in the intermediate code after parsing, things are often implemented as various stacks and operators. It depends on how the stack works as to the order of teh function calls in function parameters.

http://c-faq.com/expr/comma.html

Holmes4, the link has references to the standard.

From K&$II page 63:

The commas that separate function arguments, variables in declarations, etc., are not comma operators and do not guarantee left to right evaluation.

I'm not sure this will work, but how about:

 sprintf(time, "%02x:%02x:%02x", Wire.read(2), Wire.read(1), Wire.read(0));

The DS1307 library has the ability to pass in the Timekeeper register you're interested in (0 = seconds, 1 = minutes, 2 = hours). They are BCD values, so you may have to mask the nibbles. The internal register pointer uses the index to place the pointer prior to a read. It auto-increments, which is why most reads and writes to the RTC start with an argument of zero.

Let us know what happens.

The best bet is to just use some temporary variables. The compiler is smart enough to optimise them away, but it will force the order of execution.

But your idea will work, too.