Howto redirect (convert from hex to dec/Serial.print) fcn output into a var?

Hello! Very new to Hex/Dec conversion, and I haven't been able to locate the right arduino/C++ tutorial yet.

I have the following function to retrieve datetime information from a real-time clock (RTC) chip on the Adafruit data logger shield:

void displayTime(void) {
  uint8_t r[8];
  if (!readDS1307(0, r, 8)) {
    PgmPrintln("Read Failed for display time");
    return;
  }
  PgmPrint("The current time is ");
  // month
  hexPrint(r[5]);
  Serial.write('/');
  // day
  hexPrint(r[4]);
  PgmPrint("/20");
  // year
  hexPrint(r[6]);
  Serial.write(' ');
// and so on for the time variables.
}

The hexPrint function referenced above is:

void hexPrint(uint8_t v) {
  Serial.print(v >> 4, HEX);
  Serial.print(v & 0XF, HEX);
}

And it all works great, echoing the correctly-set date through my Mega 2560's serial output. However, I'd like to capture the output--human-readable date-- into the datalogger's line-by-line entries. How do I modify the displayTime/hexPrint function to save the readable date into a variable, not jet it out through serial?

Thank you!

Where is this function:

readDS1307(0, r, 8)

defined/implemented?

You can use the sprintf() function to format values into a string, instead of using Serial.printf to format them as it sends them over the serial port.

Sprintf supports a wide range for formatting operations including decimal and hex display of integers.

PaulS:
Where is this function:

readDS1307(0, r, 8)

defined/implemented?

Hi, PaulS,

Wow, you guys are fast! The RTC read function is:

uint8_t readDS1307(uint8_t address, uint8_t *buf, uint8_t count) {
  // issue a start condition, send device address and write direction bit
  if (!rtc.start(DS1307ADDR | I2C_WRITE)) return false;

  // send the DS1307 address
  if (!rtc.write(address)) return false;

  // issue a repeated start condition, send device address and read direction bit
  if (!rtc.restart(DS1307ADDR | I2C_READ))return false;

  // read data from the DS1307
  for (uint8_t i = 0; i < count; i++) {

    // send Ack until last byte then send Ack
    buf[i] = rtc.read(i == (count-1));
  }

  // issue a stop condition
  rtc.stop();
  return true;
}

LoganPark:
Hello! Very new to Hex/Dec conversion, and I haven't been able to locate the right arduino/C++ tutorial yet.

sprintf may be what you need. You define a "template" of what you want to print, then "sprintf" it to a buffer and print the buffer. For example:

void setup() {
        Serial.begin(9600);
        example1(); // run "conventional" example
        example2(); // run "string in PROGMEM" example
}


void example1(void) // this example wastes ram by putting the mask string in ram
{
        char *buffer; // pointer to buffer
        buffer = (char *) malloc((128) * sizeof(char)); // allocate 128 bytes for a buffer
        if(!buffer) {
                Serial.println("Failed to allocate buffer!");
                return;
        }
        const char *mask = {
                "(conventional)\tHello! My name is %s and I am %d years old which is 0x%02X in hex\r\n"
        };
        sprintf(buffer, mask, "Roger", 55, 55);
        Serial.print(buffer);
        free(buffer); // release memory
}

void example2(void) // this is a better example which stores the mask string in flash memory
{
        char *buffer; // pointer to buffer
        buffer = (char *) malloc((128) * sizeof(char)); // allocate 128 bytes for a buffer
        if(!buffer) {
                Serial.println("Failed to allocate buffer!");
                return;
        }
        const char *mask = {
                PSTR("(PROGMEM)\tHello! My name is %s and I am %d years old which is 0x%02X in hex\r\n")
        };
                sprintf_P(buffer, mask, "Roger", 55, 55);
        Serial.print(buffer);
}

void loop()
{
}

Notice the differences... PSTR("string info") places the string in flash memory and sprint**_F** is used to extract the string from flash memory.

NOTE! sprintf and sscanf do not support FLOATING POINT NUMBERS. If you try to use floating point in your program (for example, mask = { "The temperature is %f degrees\r\n" }, then sprintf(buffer, mask, 79.4); ), it will fail. The temperature will display as a question mark! The result would be "The temperature is ? degrees" instead of "The temperature is 79.4 degrees".

The people who wrote the Arduino code felt that leaving out floating point support was good because it uses about 1.5K more code (i.e. your sketches end up about 1.5K larger). But if you want full floating point support for sprintf and sscanf, use this script to modify your libc.a files (it removes the crippled functions and replaces them with the full functions). Note though that your sketches will be about 1.5K larger if you do this.

Script "fixfp" - installs floating point support

#!/bin/bash
################################################################################
# fixfp - script to install floating point support into Arduino printf library
#
# For more information, see this post:
# http://arduino.cc/forum/index.php/topic,124809.msg938573.html#msg938573
################################################################################

STATUS=0

## Exit if libc.a isn't here
test -e libc.a
if [ ${?} -ne 0 ]; then {
        echo "File 'libc.a' not found - exiting"
        exit 0
} fi

test -e libc.a.orig
let STATUS+=${?}

test -e vfprintf_flt.o
let STATUS+=${?}

test -e vfscanf_flt.o
let STATUS+=${?}

if [ $STATUS -eq 0 ]; then {
        echo "Floating point patch already performed - exiting"
        exit 0
} else {
        cp libc.a libc.a.orig
        ar -dv libc.a vfprintf_std.o
        ar -dv libc.a vfscanf_std.o
        ar -xv libprintf_flt.a vfprintf_flt.o
        ar -xv libscanf_flt.a vfscanf_flt.o
        ar -rv libc.a vfprintf_flt.o
        ar -rv libc.a vfscanf_flt.o
        echo "Floating point patch installed."
} fi

Change to the directory that contains your libc.a files (typically "arduino-1.0.1/hardware/tools/avr/lib/avr/lib"), then run the script. In addition to fixing the floating point code, it will also make backups of the original libraries so you can go back if you wish.

If you use Windows cough then you will need to find where your libraries are stored and modify the script to be a batch file. I have no idea how to do that (maybe someone else can chime in and tell you?).

Hope this all helps.

-- Roger

Ok, I have been poring over the linux man pages for (s)printf, and have come up with:

char SD_datetime[100];

 sprintf(SD_datetime, "%s-%s-%s-%.2d-%.2d-%.2d", r[6], r[5], r[4], r[2], r[1], r[0]);
  Serial.print("||||| sprintf version: ");
  Serial.print(SD_datetime);
  Serial.print("|||||");

where the r array is filled by

readDS1307(0, r, 8)

and readDS1307() is

uint8_t readDS1307(uint8_t address, uint8_t *buf, uint8_t count) {
  // issue a start condition, send device address and write direction bit
  if (!rtc.start(DS1307ADDR | I2C_WRITE)) return false;

  // send the DS1307 address
  if (!rtc.write(address)) return false;

  // issue a repeated start condition, send device address and read direction bit
  if (!rtc.restart(DS1307ADDR | I2C_READ))return false;

  // read data from the DS1307
  for (uint8_t i = 0; i < count; i++) {

    // send Ack until last byte then send Ack
    buf[i] = rtc.read(i == (count-1));
  }

  // issue a stop condition
  rtc.stop();
  return true;
}

The output I get is... blank.

Krupsi, I will next work on adapting your example into the code to see what happens. Thank you for providing a detailed answer!

The %s format specifier is to print a string. The elements in the r array are not strings.

They are bytes. Use %d, instead. Or, %02d to get 2 characters, with a leading 0 if the value is less than 10 (04, 08, 22).

Excellent! I appreciate your guidance. Here's the output:

sprintf datetime is: 18-17-04_17:81:09, Sun,
which should be:
sprintf datetime is: 12-11-04_11:51:09, Sun.

So, I think the sprintf codeblock is correctly recognizing input from the r[] array. The code is getting close; I think I need to bitshift or some similar operation. I notice that when a byte is reported incorrectly, it is off by six, typically. So that makes me wonder if I'm failing to convert to/from hex/dec.

Here's the codeblock based on everyone's input and the man page:

const char *SD_datetime_mask = 
  {
    PSTR("sprintf datetime is: %02d-%2d-%.2d_%.2d:%.2d:%.2d, %s ")
  };

  sprintf_P(SD_datetime, SD_datetime_mask, r[6], r[5], r[4], r[2], r[1], r[0], Ddd[r[3]]);
  Serial.print(SD_datetime);

Elsewhere, the functional code that goes straight to Serial.print uses:

  Serial.print(v >> 4, HEX);
  Serial.print(v & 0XF, HEX);

I think I need to apply those operations somehow to my data before displaying the sprintf'ed string. Am I on the right track?

LoganPark:
I think I need to apply those operations somehow to my data before displaying the sprintf'ed string. Am I on the right track?

I don't have the clock chip you are using, but I am familiar with the Dallas (now Maxim) SmartClock chips (DS-1216).

They return all data as BCD numbers... for example, the current month (November) is returned as "0x11" which is actually 17 in decimal (your "off by six" problem?).

You have to take the upper 4 bits and multiply by 10, then add that to the lower 4 bits to get the decimal version of the BCD number.

Hope this helps....

Success!

I googled around for a BCD --> DEC conversion utility function and found

uint8_t bcdToDec(uint8_t val)
{
  return ( (val / 16 * 10) + (val % 16));
}

And the output appears to be correct. Thanks again.

LoganPark:
Success!

Great! Glad you got it working.