Pages: [1]   Go Down
Author Topic: Print hex with leading zeroes  (Read 13027 times)
0 Members and 1 Guest are viewing this topic.
Vancouver, Canada
Offline Offline
Newbie
*
Karma: 0
Posts: 8
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

You can print hex using the serial library with Serial.print(data,HEX), but it does not include leading zeroes, so 0x01 will come out as 1, etc. This can get confusing if you print an array, so I put together some simple functions to include leading zeroes. In case they are useful to others, here they are:

Code:
/*
  PrintHex routines for Arduino: to print byte or word data in hex with
  leading zeroes.
  Copyright (C) 2010 Kairama Inc

  This program is free software: you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation, either version 3 of the License, or
  (at your option) any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/
void PrintHex8(uint8_t *data, uint8_t length) // prints 8-bit data in hex with leading zeroes
{
        Serial.print("0x");
        for (int i=0; i<length; i++) {
          if (data[i]<0x10) {Serial.print("0");}
          Serial.print(data[i],HEX);
          Serial.print(" ");
        }
}

void PrintHex16(uint16_t *data, uint8_t length) // prints 16-bit data in hex with leading zeroes
{
        Serial.print("0x");
        for (int i=0; i<length; i++)
        {
          uint8_t MSB=byte(data[i]>>8);
          uint8_t LSB=byte(data[i]);
          
          if (MSB<0x10) {Serial.print("0");} Serial.print(MSB,HEX); Serial.print(" ");
          if (LSB<0x10) {Serial.print("0");} Serial.print(LSB,HEX); Serial.print(" ");
        }
}

Example of how to use them:

Code:
void setup()
{
        Serial.begin(9600);
        Serial.print("Setup: Examples of PrintHex usage\n\n");
}

void loop()
{
        uint8_t ByteData[5]={0x01, 0x0F, 0x10, 0x11, 0xFF};
        Serial.print("With uint8_t array:  "); PrintHex8(ByteData,5); Serial.print("\n");

        uint8_t ByteDatum=0x01;
        Serial.print("With uint8_t scalar: "); PrintHex8(&ByteDatum,1); Serial.print("\n");
 
        uint16_t Shorts[5]={0x0001, 0x00FF, 0x0100, 0xAAAA, 0xFFFF};
        Serial.print("With uint16_t array: "); PrintHex16(Shorts,5); Serial.print("\n");

        Serial.print("==========================================\n");
        delay(10000);
}

The output of which is:
Code:
Setup: Examples of PrintHex usage

With uint8_t array:  0x01 0F 10 11 FF
With uint8_t scalar: 0x01
With uint16_t array: 0x00 01 00 FF 01 00 AA AA FF FF
==========================================
Logged

0
Offline Offline
Newbie
*
Karma: 0
Posts: 19
Arduino rocks
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

Hi there!

Thanks for the hint!

My suggestion would be doing something like:

Code:
void printHex(int num, int precision) {
      char tmp[16];
      char format[128];

      sprintf(format, "0x%%.%dX", precision);

      sprintf(tmp, format, num);
      Serial.print(tmp);
}

void loop()
{
      printHex(375, 8);
      for(;;);
}

Hope it helps!

Regards,

bootsector
Logged

Vancouver, Canada
Offline Offline
Newbie
*
Karma: 0
Posts: 8
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

sprintf is so much cleaner - I didn't know it was available! Thx a lot.
Logged

0
Offline Offline
Newbie
*
Karma: 0
Posts: 19
Arduino rocks
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

np man!

Just be careful with the array limits. Maybe adding some size checks at the beginning of the function would help!

Regards,

bootsector
Logged

Vancouver, Canada
Offline Offline
Newbie
*
Karma: 0
Posts: 8
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

New code based on sprintf as suggested (but without any bounds checking smiley-sad)...

Code:
/*
  PrintHex routines for Arduino: to print byte or word data in hex with
  leading zeroes.
  Copyright (C) 2010 Kairama Inc

  This program is free software: you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation, either version 3 of the License, or
  (at your option) any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/
void PrintHex8(uint8_t *data, uint8_t length) // prints 8-bit data in hex with leading zeroes
{
      char tmp[16];
        for (int i=0; i<length; i++) {
          sprintf(tmp, "0x%.2X",data[i]);
          Serial.print(tmp); Serial.print(" ");
        }
}

void PrintHex16(uint16_t *data, uint8_t length) // prints 16-bit data in hex with leading zeroes
{
        char tmp[16];
        for (int i=0; i<length; i++)
        {
          sprintf(tmp, "0x%.4X",data[i]);
          Serial.print(tmp); Serial.print(" ");
        }
}

Use it the same way...
Code:
void setup()
{
        Serial.begin(9600);
        Serial.print("Examples of PrintHex usage\n\n");
}

void loop()
{
        uint8_t ByteData[5]={0x01, 0x0F, 0x10, 0x11, 0xFF};
        Serial.print("With uint8_t array:  "); PrintHex8(ByteData,5); Serial.print("\n");

        uint8_t ByteDatum=0x01;
        Serial.print("With uint8_t scalar: "); PrintHex8(&ByteDatum,1); Serial.print("\n");
 
        uint16_t Shorts[5]={0x0001, 0x00FF, 0x0100, 0xAAAA, 0xFFFF};
        Serial.print("With uint16_t array: "); PrintHex16(Shorts,5); Serial.print("\n");

        Serial.print("==========================================\n");
        delay(10000);
}

and get these results:
Code:
Examples of PrintHex usage

With uint8_t array:  0x01 0x0F 0x10 0x11 0xFF
With uint8_t scalar: 0x01
With uint16_t array: 0x0001 0x00FF 0x0100 0xAAAA 0xFFFF
Logged

0
Offline Offline
Newbie
*
Karma: 0
Posts: 10
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Thanks,

This helped out alot. I used it for this http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1263301069, and altered it a bit so that my MD5 hash would print out as a continuous string of Hex, as we're used to seeing it. Check it out.
Logged

0
Offline Offline
Newbie
*
Karma: 0
Posts: 5
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Thanks,
This is very useful! smiley
Logged

0
Offline Offline
Newbie
*
Karma: 0
Posts: 2
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

For anyone not wishing to include sprintf, here is a micro-optimisation which will do the conversion directly. In my personal tests it runs (comparing both without output) approximately 15x faster, and since we avoid sprintf the resulting binary is almost 1.5kb lighter, leaving you with room for more important code. First block of code provides lower-case hex suitable for use with some applications requiring MD5 hashes (such as digest access authentication):

Code:
void PrintHex8(uint8_t *data, uint8_t length) // prints 8-bit data in hex
{
      char tmp[length*2+1];
      byte first;
      byte second;
      for (int i=0; i<length; i++) {
            first = (data[i] >> 4) & 0x0f;
            second = data[i] & 0x0f;
            // base for converting single digit numbers to ASCII is 48
            // base for 10-16 to become lower-case characters a-f is 87
            // note: difference is 39
            tmp[i*2] = first+48;
            tmp[i*2+1] = second+48;
            if (first > 9) tmp[i*2] += 39;
            if (second > 9) tmp[i*2+1] += 39;
      }
      tmp[length*2] = 0;
      Serial.println(tmp);
}

Adapting this to uint16 is trivial. If you want the original formatting seen in Mike Jacksons latest post then see the following modifications:

Code:
void PrintHex8(uint8_t *data, uint8_t length) // prints 8-bit data in hex
{
      char tmp[length*5+1];
      byte first;
      byte second;
      for (int i=0; i<length; i++) {
            first = (data[i] >> 4) & 0x0f;
            second = data[i] & 0x0f;
            // base for converting single digit numbers to ASCII is 48
            // base for 10-16 to become upper-case characters A-F is 55
            // note: difference is 7
            tmp[i*5] = 48; // add leading 0
            tmp[i*5+1] = 120; // add leading x
            tmp[i*5+2] = first+48;
            tmp[i*5+3] = second+48;
            tmp[i*5+4] = 32; // add trailing space
            if (first > 9) tmp[i*5+2] += 7;
            if (second > 9) tmp[i*5+3] += 7;
      }
      tmp[length*5] = 0;
      Serial.println(tmp);
}
note: I deleted my post from yesterday & reposted to include some new changes to the code
Logged

Global Moderator
Netherlands
Offline Offline
Shannon Member
*****
Karma: 224
Posts: 13915
In theory there is no difference between theory and practice, however in practice there are many...
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

@Robert
15 times faster is very impressive, so I felt challenged and fabricated this optimized version based upon your code. Most was won by concentrating all actions per half byte. The footprint is a bit smaller (48bytes if i'm correct) and my measurements indicated ~25% off (measured without the actual printing). Thanks for the inspiration smiley-wink

Code:
void PrintHex83(uint8_t *data, uint8_t length) // prints 8-bit data in hex
{
  char tmp[length*2+1];
  byte first ;
  int j=0;
  for (uint8_t i=0; i<length; i++)
  {
    first = (data[i] >> 4) | 48;
    if (first > 57) tmp[j] = first + (byte)39;
    else tmp[j] = first ;
    j++;

    first = (data[i] & 0x0F) | 48;
    if (first > 57) tmp[j] = first + (byte)39;
    else tmp[j] = first;
    j++;
  }
  tmp[length*2] = 0;
  // Serial.println(tmp);
}
« Last Edit: January 11, 2011, 05:19:34 pm by robtillaart » Logged

Rob Tillaart

Nederlandse sectie - http://arduino.cc/forum/index.php/board,77.0.html -
(Please do not PM for private consultancy)

0
Offline Offline
Newbie
*
Karma: 0
Posts: 2
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Thanks robtillaart. Im running out of space so those 48 bytes might make all the difference smiley-grin . Just finished implementing digest access authentication for my webserver, between ethernet, MD5, and SD libs there isnt much room for control code.
Logged

Global Moderator
Netherlands
Offline Offline
Shannon Member
*****
Karma: 224
Posts: 13915
In theory there is no difference between theory and practice, however in practice there are many...
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Maybe other functions can be optimized too. If you have one that could be optimized just post it on the forum. Preferably together with a testapp that measures the speed. Something simple like:

Code:
void loop()
{
  long start = millis();
  for (int i=0; i< 10000; i++) PrintHex8(ar, 15);
  Serial.println(millis() - start);

  start = millis();
  for (int i=0; i< 10000; i++) PrintHex83(ar, 15);
  Serial.println(millis() - start);
  delay(3000);
}  
« Last Edit: January 12, 2011, 09:13:10 am by robtillaart » Logged

Rob Tillaart

Nederlandse sectie - http://arduino.cc/forum/index.php/board,77.0.html -
(Please do not PM for private consultancy)

Pages: [1]   Go Up
Jump to: