5
\$\begingroup\$

I am using an ADXL345 triple axis accelerometer via a FEZmini1.3 .Net Micro Framework board in a project I am busy with.

The ADXL345 provides data in 13-bit 2's complement.

How do you decode this into decimal?

I have implemented a BitConverter (As provided by Ravenheart (Toshko)) in the project, but surely this assumes a full 16 bits (or two bytes) worth of data, where the most significant bit will always be the sign bit?

In a 13 bit number the 16th bit will always be 0, won't it?

       ------------Byte 1------------   ------------Byte 2-----------
bit#:  15  14  13  12  11  10   9   8   7   6   5   4   3   2   1   0
16 bit: 1   1   1   1   1   1   1   1   1   1   1   1   1   1   1   1 = -32767
13 bit: 0   0   0   1   1   1   1   1   1   1   1   1   1   1   1   1 = -4095

but if the 13 bit number is being converted by an algorithm assuming a full 16 bits the number will equal 8192.

Is my understanding correct? If yes, how do I go about converting a 13 bit 2's complement number into decimal?

Edit (After getting some help from my friends below): So thanks to the responses I now know that my assumptions of 2's compliment was wrong, so for clarity when anyone reads this thread I wanted to correct my initial statement:

       ------------Byte 1------------   ------------Byte 2-----------
bit#:  15  14  13  12  11  10   9   8   7   6   5   4   3   2   1   0
16 bit: 1   1   1   1   1   1   1   1   1   1   1   1   1   1   1   1 = -1
13 bit: 0   0   0   1   1   1   1   1   1   1   1   1   1   1   1   1 = -1

In 2's Compliment the numbers count up to the halfway point and then start counting down, so:

00000000 = 0
00000001 = 2
00000010 = 3
00000011 = 4
.
.
.
01111110 = 126
01111111 = 127
10000000 = -128
10000001 = -127
10000010 = -126
.
.
.
11111100 = -4
11111101 = -3
11111110 = -2
11111111 = -1
\$\endgroup\$
1
  • \$\begingroup\$ Why do you need decimal? \$\endgroup\$
    – starblue
    Jul 13, 2011 at 16:17

5 Answers 5

5
\$\begingroup\$

To decode the number into decimal, convert it to something your processor understands natively, then use the existing binary to decimal conversion capabilities to convert to decimal. To convert to native integer representation, all you have to do is sign extend:

II is native signed integer
II <-- 13 bit signed A/D value  (get the A/D result)
II <-- II & 1FFFh               (make really sure limited to 13 bits)
if II >= 1000h                   (negative value ?)
  then II <-- II - 2000h        (convert to native negative)
Note that this is independent of how wide the native signed integer is, as long as it's more than 13 bits. This means this technique works for both 16 bit and 32 bit signed integers.

Edit: fixed details of sign extending above. I wrote it right on the scribble paper next to my keyboard, then copied it into the post wrong. Also added note about it being independent of the native integer size.

\$\endgroup\$
4
  • \$\begingroup\$ Huh? - As an 'Int13', 1001h represents -4095d but subtracting 1000h would result in 1. Also, 1000h represents -4096 as an 'Int13' but if left unmodified represents +4096 as an Int16 \$\endgroup\$
    – MikeJ-UK
    Jul 13, 2011 at 12:12
  • \$\begingroup\$ @Mikej: I copied from my scribbles incorrectly, and you may be referring to before I edited to fix that. Let's say the A/D value is 1FFFh. The A/D means -1 with that, but will be 8191 when copied directly into a larger native integer. Subtracting 2000h (8192) from it results in the desired -1. This works for all negative numbers. -2 comes out of the A/D as 1FFEh = 8190. 8190 - 8192 = -2 as desired. \$\endgroup\$ Jul 13, 2011 at 12:24
  • \$\begingroup\$ That's better but the conditional test should be >=. The bit-width independence is a plus though. \$\endgroup\$
    – MikeJ-UK
    Jul 13, 2011 at 12:58
  • \$\begingroup\$ @Mikej: Yup, found and fixed the >= right before seeing your comment. Argh, I wonder how many more little things I can get wrong in two lines of code!? \$\endgroup\$ Jul 13, 2011 at 13:26
3
\$\begingroup\$

The 6809 microprocessor (most beautiful 8-bitter ever!) had a SEX instruction, short for "Sign EXtend" which converted an 8-bit 2's complement number to 16-bit (the 6809 could combine it's two 8-bit accumulators into one 16-bit accumulator). If the highest bit was 1 (negative) then the high order byte would become 0xFF, otherwise 0x00. So a sign extend actually copies the sign bit to all previous bits. You can OR with 0xE000 if bit 12 is set:

if (adxl & 0x1000) {
  adxl |= 0xE000 };

To convert to decimal (BCD) Atmel has this interesting appnote, which has algorithms for BCD-to-binary and vice versa:

16-bit Binary to 5-digit BCD Conversion
8-bit Binary to 2-digit BCD Conversion
5-digit BCD to 16-bit Binary Conversion
2-digit BCD to 8-bit Binary Conversion
2-digit Packed BCD Addition
2-digit Packed BCD Subtraction

The algorithms are processor-independant, though Atmel provides source code for the AVR.

\$\endgroup\$
1
\$\begingroup\$

Shift left three times? That would then give you a 16 bit signed integer that would match the range of the other numbers you are working with.

You won't have the low-order resolution of your other sensors, but the values will be a lot easier to work with.

00011111 11111111
Shift Left
00111111 11111110
Shift Left
01111111 11111100
Shift Left
11111111 11111000

In C you can use

val = val << 3;

In ASM there will be appropriate shift left commands. You may need to shift individual bytes and use the carry flag to pass bits from the low to high bits.

\$\endgroup\$
9
  • \$\begingroup\$ You may have to clear the carry flag before shifting. \$\endgroup\$
    – stevenvh
    Jul 13, 2011 at 11:14
  • \$\begingroup\$ Yup - clear carry, shift low, store carry, shift high, set/clear low bit of high depending on stored carry. \$\endgroup\$
    – Majenko
    Jul 13, 2011 at 11:16
  • \$\begingroup\$ Or - shift high, clear carry, shift low, examine carry, set/reset low bit of high byte. \$\endgroup\$
    – Majenko
    Jul 13, 2011 at 11:17
  • \$\begingroup\$ Looks OK for a 16-bit or 32-bit controller, expensive for 8-bit. In that case the OR-ing I mention in my answer is less involved. Only three instructions: bit test, conditional jump, OR. \$\endgroup\$
    – stevenvh
    Jul 13, 2011 at 11:18
  • \$\begingroup\$ But then you have to ask yourself is that extra efficiency in the sign extending going to make your maths later more complex, or would it be better to spend a few more instruction cycles in the sign manipulation to then leave yourself with number ranges that match your other data, thus making your other calculations simpler? Without knowing the rest of the OP's program, who can say? \$\endgroup\$
    – Majenko
    Jul 13, 2011 at 11:25
1
\$\begingroup\$

You need to do sign extension, as the others said.

There are various methods, see Hacker's Delight in Chapter 2 Basics on page 18.

Edit: I like the second method, adapted to your case it becomes

(x ^ 0x1000) – 0x1000

assuming the three most significant bits are always zero (^ is exclusive or).

\$\endgroup\$
2
  • \$\begingroup\$ It would be nice if you would quote the method here. \$\endgroup\$
    – stevenvh
    Jul 13, 2011 at 13:59
  • \$\begingroup\$ The whole section was a bit long, but I now adapted my favorite method. \$\endgroup\$
    – starblue
    Jul 13, 2011 at 14:28
1
\$\begingroup\$

If you have a 13-bit 2's complement value, read it into a 16-bit signed variable, shift it left by 16-13 = 3 bits, then shift it right by three bits, done.

int16_t value = readValue(); // readValue returns a 13-bit 2's complement number
value = (value << 3) >> 3;   // sign extend the value

the value variable now holds the actual decimal value.


Edit In light of @Olin's comment, I want to be clear in my response. Here in more elaborate terms is how 0001 1111 1111 1111 becomes 1111 1111 1111 1111 explicitly (per the OP's example), if I understand the question correctly. The below is representative C code.

uint8_t byte1 = 0x1F; //0b00011111
uint8_t byte2 = 0xFF; //0b11111111
int16_t value = (((uint16_t) byte1) * 256) + (byte2); // value now contains 0b0001111111111111
value = (value << 3) >> 3;                            // value now contains 0b1111111111111111
\$\endgroup\$
6
  • \$\begingroup\$ No it doesn't. The variable will now hold the twos complement binary value, assuming your language performs a arithmetic right shift when the number is signed. Modern mainstream computers hold numbers in binary, not decimal, and your shifting strategy even depends on it! \$\endgroup\$ Jul 13, 2011 at 18:24
  • \$\begingroup\$ @Olin I was assuming C. I was further assuming that the OP was going for a sensible output from something like printf("%d", value); \$\endgroup\$
    – vicatcu
    Jul 13, 2011 at 19:56
  • \$\begingroup\$ That's all reasonable, but your statement "variable now holds the actual decimal value" is still incorrect. \$\endgroup\$ Jul 13, 2011 at 20:05
  • \$\begingroup\$ @Olin I think you're splitting hairs now. Ultimately the variable refers to a memory location holding a series of bits. The value of those bits is whatever you interpret it to be. By virtue of assigning it to an int16_t I am giving it meaning, i.e. a 16-bit signed integer. The value of this integer is what the OP would expect per his interpretation of the 13-bit signed integer (which I believe is consistent with 2's complement with sign extension). C provides for sign-extension when using the >> operator on signed data types. \$\endgroup\$
    – vicatcu
    Jul 13, 2011 at 20:12
  • \$\begingroup\$ Yes, that's all fine. I'm only objecting to your statement that the variable holds a decimal value. It doesn't. It holds a binary value. \$\endgroup\$ Jul 13, 2011 at 20:20

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.