Dealing with 40 bits

HI All,

I am working with a RFID reader that sends the RFID number to our particle in 5 HEX bytes. The human readable version of this is essentially a 40 bit word. As this is bigger than an unsigned long, what is the best way for me to convert these 5 Hex bytes into a potentially 12 digit human readable number?

Ultimately this number will be converted to a char array to be published as the identifying number of the RFID tag that has been read.

CONCATENATING BYTES

Incase someone else is looking at this in the future, for verification, the bytes I am concatenating 31 9A 54 DE 9A. FYI these bytes are stored backwards in my rfidData array. i.e byte 31 is rfidData[11], 9A is rfidData[10] etc

uint64_t val = 0;
val += (uint64_t)rfidData[11] << 32;
val += (uint64_t)rfidData[10] << 24;
val += (uint64_t)rfidData[9] << 16;
val += (uint64_t)rfidData[8] << 8;
val += (uint64_t)rfidData[7];
Serial.println(val);

You can use uint64_t (aka long long) - although I’m not entirely sure that printf() and its siblings do treat them correctly on Particle devices - still worth a test tho’.
You may need to come up with your own string translation.

1 Like

Seems to be printing successfully:

ADDING BYTES: 213042650778
1 Like

Obviously Serial.print() is not using a printf()-relative internally.

However, it would be good if Particle could some time switch to a compiler that also supports %lld and %llu to prevent the confusion like this

  uint64_t x = 0x123456789ABCDEF0;
  Serial.print(x);          // prints 1311768467463790320
  Serial.printf("%llu", x); // prints lu 
2 Likes

It is true, sprintf-style formatting, including snprintf() , Log.info() , Serial.printf() , String::format() etc. do not support 64-bit integers. They do not support %lld , %llu or Microsoft-style %I64d or %I64u .

As a workaround you can use the Print64 firmware library in the community libraries. The source and instructions can be found in GitHub.

4 Likes

EDIT: DOESN’T WORK, KEEP READING

Another nice workaround is the following, although please tell me if I am doing something I shouldn’t below:

uint64_t val = 123456789012;
uint32_t low = val % 0xFFFFFFFF;
uint32_t high = (val>>32) % 0xFFFFFFFF;
char s[12];
snprintf(s, sizeof(s), "%02u%010u", high, low);

This was useful for me because I wanted to keep leading 0’s as they represent part of the RF Tag ID.

I don’t think that works with decimal values.
Just do the test with a 2-byte value

  uint16_t w = 0xABCD;            //         == 43981
  uint8_t low = w % 0xFF;         // == 0xCD == 205
  uint8_t high = (w >> 8) % 0xFF; // == 0xAB == 171
  printf("%02u%02u", high, low); // results in 171205 != 43981

BTW, the modulo (%) would imply a highly expensive division. When dealing with bits you’d rather want to do bit logic (e.g. low = val & 0xFFFFFFFF).

1 Like

Yeah I spoke too soon, It worked on my first test as my high byte was all zeros. but after testing with 0xFFFFFFFFFF it broke down.

Hmm, so my parameters are:

  • The number val will always be 5 hex bytes.
  • char array s needs to be the same length, keeping leading 0’s if necessary.

I would use Print64 library but after all my hard work removing Strings from my firmware, I would like to avoid them if possible.

It’s not difficult to write your own bin string converter.

However, who will really ever read these numbers?
Wouldn’t it be thinkable to just stick with HEX encoding?

1 Like

thanks, might look into making a bin string converter then so.

Unfortunately our customers read these tags, so would like to keep the same format to avoid backend and mobile app changes.

You can use the logic from Rick’s Print64.
He does work with a char array internally and only converts it to String right at the end.

1 Like

So I just made the following based on Ricks Library.

char * toChar(uint64_t value, unsigned char base) {
    if (base > 16) {
        base = 16;
    }
    char temp[68];
    char *cp = &temp[sizeof(temp) - 1];
    *cp = 0;

    if (value != 0) {
        while(value != 0) {
            *--cp = _toAscii[value % base];
            value /= base;
        }
    }
    else {
        *--cp = '0';
    }

    static char * result(cp);              

    return result;
}

Is using a static char * the best idea here?

No, that approach won’t work. You are making a static copy of the pointer, but the underlying character array is declared local on the stack and will not survive the function return. Using the pointer after the function returns will result in undefined behavior and probably a hard fault.

I have not seen Rick’s code but I would guess that he converted the local character array into a String object in order to extend the lifetime of the string so it can be used outside the function.

One possible solution would be to declare the temp character array as static so that it survives the call. Not efficient though since 68 bytes of memory will be reserved for this purpose only.

2 Likes

Yup, that's exactly what he does!

I’m with @Muskie on that, but I’d go down the route most standard functions dealing with strings go: Pass in your array you want populated and work on that instead of working on a temporary array

You just want to add some sanity checks about the length of the buffer to avoid overflow errors.

Just for geeks: Instead of [value % base] and value /= base you could use lldiv() for some extra speed :wink:

2 Likes

Great info thanks everyone. As I am new to pointers and referencing, can someone help explain the logic behind Ricks function?

String toString(uint64_t value, unsigned char base) {
    if (base > 16) {                
        base = 16;
    }
    char temp[68];
    char *cp = &temp[sizeof(temp) - 1]; 
    *cp = 0;

    if (value != 0) {
        while(value != 0) {
            *--cp = _toAscii[value % base];
            value /= base;
        }
    }
    else {
        *--cp = '0';
    }

    String result(cp);

    return result;
}

Whole String function above for reference

    if (base > 16) {                
        base = 16;
    }
  1. I assume this is just to keep the number below 16?
    char temp[68];
    char *cp = &temp[sizeof(temp) - 1]; 
    *cp = 0;
  1. Why 4 extra chars ?
  2. The second line above declares a pointer to a char so that cp contains the address of the last spot in the char array.
  3. Then we set the pointer for cp to 0?
if (value != 0) {
        while(value != 0) {
            *--cp = _toAscii[value % base];
            value /= base;
        }
    }
    else {
        *--cp = '0';
    }
  1. So here the value to be converted is just divided by base and the remainder is placed at the back of the temp array? And this dividing continues until all is said and done?
String result(cp);
  1. This syntax confused me a bit, is it just creating a String variable called result and letting the first value be the front of cp?
  1. Yes he is limiting the base of the number. Arithmetic on a base greater than 16 is unlikely to be useful.
  2. Not sure why 4 extra but there needs to be at least one extra position for the trailing null character. Since base 2 generates the longest string, 64 characters are needed to represent the number plus an extra for null terminator. Anyone else see why the extra 3?
    3 This line creates a pointer to the last entry in the array.
  3. Adds the null terminator at the last entry so the final result is properly terminated.
  4. He is using modulo arithmetic to extract the digits. For example, the number 47 modulo 10 leaves 7, which is the least significant digit. Thus, he extracts the digits in reverse order, LSD first, then the next and so on. Each digit is placed starting at the end of the array moving towards the front (and the MSD). While the value is not zero he adds another digit by modulo division and then uses normal divide to get the result of the divide. Again 47 divided by 10 leaves 4. The number gets smaller on each iteration of the loop until the value is zero and all digits have been written.
  5. The string value is being initialized by a pointer to the very first character of the string, which happens to be the last digit he wrote to the array and is the final value of cp. When initializing a String with a pointer to a character array, the String gets populated by the actual array contents itself, not just the first character. Now String has the final answer, saved on the heap where it is safe to return to the caller.
3 Likes

Muskie is correct.

The +4 is because it needs to be at least 1 for the trailing null, and the compiler aligns to 4-byte alignment anyway, and this adds an extra buffer space so the buffer is not overwritten if I happened to have an off-by-one error somewhere and it doesn’t use any more stack space in reality.

4 Likes

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