Log base 2 limitation problem with 16 button TM1638 module

I bought a TM1638 Display Module with Common Anode 8 LED with 16 Buttons. I seem to have run into some sort of limitation on the math I am doing in the sketch that reads the buttons and displays the numbers.

First, I don’t recommend this module for a couple of reasons.

  • The quad LED devices are common anode, rather than common cathode that Ricardo Batista’s tm1638-library-master is written for.
  • I think that library also is limited to decoding 8 buttons.

I was able to overcome those limitations by using the modifications to the standard library posted by 1958Brian here: TM1638 display Common Anode 8 LED with 16 buttons - Using Arduino / Displays - Arduino Forum

The sixteen buttons return values that are powers of 2. And they are not in one sequence. Odd powers of 2 are returned by the first two rows, and even powers of 2 by the third and fourth rows. The highest returned value is 32,768. To convert those returned values to numbers from 0 to 15, I computed the log base 2 of the returned value. I then used that converted number as an index of an array of values as a key map.

Here is the problem. The natural log(returned value) function seems to return the correct value. But when I calculate the log base2 of 16,384 and 32,768 I get the same result: the correct answer for 16,384.

Here is my code. It is a snippet of the actual sketch without the LED handling:

uint16_t keyindex;
//long keyvalues[] = {8, 0, 9, 1, 10, 2, 11, 3, 12, 4, 13, 5, 14, 6, 15, 7};
long keyvalues[] =     {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16};
long powersOfTwo[17] = {1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768};
uint16_t key;  // the pressed key value - each key is 1 bit in this unsigned int
float currValue = 0;


void setup() {
   Serial.begin(9600); 
}

void loop() {
  currValue = 0;
  // Loop through each of the possible key values
  for (int i = 0; i <= 15; i++) {
    key = powersOfTwo[i];

    Serial.print("Key pressed: ");
    Serial.print(key);

    Serial.print(" - log (key): ");
    Serial.print(log (key));

    keyindex = log (key) / log (2);
    Serial.print(" - keyindex: ");
    Serial.print(keyindex);
    
    currValue = currValue * 10 + keyvalues[keyindex];
    Serial.print(" - currValue: ");
    Serial.println(currValue);

    delay(500);
  }
}

Here is the Serial Monitor debug results:
Key pressed: 1 - log (key): 0.00 - keyindex: 0 - currValue: 0.00
Key pressed: 2 - log (key): 0.69 - keyindex: 1 - currValue: 1.00
Key pressed: 4 - log (key): 1.39 - keyindex: 2 - currValue: 12.00
Key pressed: 8 - log (key): 2.08 - keyindex: 3 - currValue: 123.00
Key pressed: 16 - log (key): 2.77 - keyindex: 4 - currValue: 1234.00
Key pressed: 32 - log (key): 3.47 - keyindex: 5 - currValue: 12345.00
Key pressed: 64 - log (key): 4.16 - keyindex: 6 - currValue: 123456.00
Key pressed: 128 - log (key): 4.85 - keyindex: 7 - currValue: 1234567.00
Key pressed: 256 - log (key): 5.55 - keyindex: 8 - currValue: 12345678.00
Key pressed: 512 - log (key): 6.24 - keyindex: 9 - currValue: 123456792.00
Key pressed: 1024 - log (key): 6.93 - keyindex: 10 - currValue: 1234567936.00
Key pressed: 2048 - log (key): 7.62 - keyindex: 11 - currValue: ovf
Key pressed: 4096 - log (key): 8.32 - keyindex: 12 - currValue: ovf
Key pressed: 8192 - log (key): 9.01 - keyindex: 13 - currValue: ovf
Key pressed: 16384 - log (key): 9.70 - keyindex: 14 - currValue: ovf
Key pressed: 32768 - log (key): 10.40 - keyindex: 14 - currValue: ovf
(I'm not worried about the overflow yet)

log() returns the natural log (base-e).

I understand that. That is why I have the line" "keyindex = log (key) / log (2);" to make it base 2.

much simpler to just do a lookup

    switch ( key ) {
      case 1:     keyindex = 0; break;
      case 2:     keyindex = 1; break;
      case 4:     keyindex = 2; break;
      case 8:     keyindex = 3; break;
      case 16:    keyindex = 4; break;
      case 32:    keyindex = 5; break;
      case 64:    keyindex = 6; break;
      case 128:   keyindex = 7; break;
      case 256:   keyindex = 8; break;
      case 512:   keyindex = 9; break;
      case 1024:  keyindex = 10; break;
      case 2048:  keyindex = 11; break;
      case 4096:  keyindex = 12; break;
      case 8192:  keyindex = 13; break;
      case 16384: keyindex = 14; break;
      case 32768: keyindex = 15; break;
    }

no log() required

Duh. That is actually a good idea. I guess I got "too clever by half", dusting off my school mathematics lessons.. Thanks, I will follow your advice.
I would still like to know what limit I was running in to.

this lookup can be easily replaced by one line:

byte i =0;
while ( (a & 1) == 0)  { a = a >> 1;  i++;}

True, but the OP stated the key values would not actually be 0,1,2,3.... but some other sequence to match the actual keys.

//long keyvalues[] = {8, 0, 9, 1, 10, 2, 11, 3, 12, 4, 13, 5, 14, 6, 15, 7};

at which point, the lookup stays almost the same but the while() loop breaks

The problem was was truncation of floating point arithmetic.

Try this for rounding to the nearest:

keyindex = (log (key)+0.4) / log (2);
1 Like

I agree.

Why 0.4 instead of the more usual 0.5? And why add it before the division?

I would have gone with

keyindex = log (key) / log (2) + 0.5;
1 Like

I was going for what would be 0.5 after the division, but I messed up while trying to work in log space because I was focused on the "key" column.

Reverse-engineering it: Halfway between units in log(2) space is sqrt(2) = 1.414, and I mistakenly subtracted a 1 because of fuzzy thinking about factors.

The proper addition prior to the division would be log(sqrt(2))=0.3465736.

In R:

 (log(2^seq(16))+log(sqrt(2)))/log(2)
 [1]  1.5  2.5  3.5  4.5  5.5  6.5  7.5  8.5  9.5 10.5 11.5 12.5 13.5 14.5
[15] 15.5 16.5
1 Like

For DaveX,
I was going to ask you where the truncation occurs, at 64 bits? But then I started searching the internet for this "feature" of JavaScript, and soon realized that I was getting into deeper language development than I really want to go. I am happy with your answer (modified to 0.5) and don't want to go farther. Thanks for your expertise.

I think it is that when you start doing log() with the integers, it becomes floating point math and there is less digits of precision than would be available in the integer math, so you can't represent log(32768) with full precision.

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