4bits to number

I have a board that has 4 data bits of output. I need to read these 4 bits and based on on the value perfom a task. Without doing a big messy if/else statement base on a digitalRead(pinx) how can I read the digialpins convert the 4 digialpins into a value. Not sure how to combine the bits into a single int value or convert it to a binary/octal value for compairson in an if statement.

Something onlong the lines of this pseudo code.

bit1 = digitalpin1
bit2 = digitalpin2
bit3 = digitalpin3
bit4 = digitalpin4

binvalue = bit1 combined with bit2, with bit3 and bit4

if (binalue = 0110 )
{
do xtask
}

or

bit1 = digitalpin1
bit2 = digitalpin2
bit3 = digitalpin3
bit4 = digitalpin4

iintvalue = bit1 combined with bit2, with bit3 and bit4

if (intvalue == 8 )
{
do xtask
}

int value = 8*bit4 + 4*bit3 + 2*bit2 + bit1; //binary weighting

value will range from 0 to 15 decimal, depending on which bits are set.

Taking jremington's lead one step further:

void func0()
{
  Serial.println("In func0");
}
void func1()
{
  Serial.println("In func1");
}
void func2()
{
  Serial.println("In func2");
}
void func3()
{
  Serial.println("In func3");
}

void (*functionPtr[])() = {func0, func1, func2, func3};   // Fill in the missing functions later...


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

void loop() {
  char buff[5];
  int charsRead;
  int mockBitValue;

  if (Serial.available() > 0) {         // This is in lieu of jremington's conversion routine...
    charsRead = Serial.readBytesUntil('\0', buff, sizeof(buff) - 1);    // Save room for null
    buff[charsRead] = '\0';
    mockBitValue = atoi(buff);

    Serial.print("mockBitValue = ");    // Show the mock value of the bits
    Serial.println(mockBitValue);

    if (mockBitValue < 4) {             // Change when you've added more functions
      functionPtr[mockBitValue]();      // Invoke the function
    }
  }
}

This code creates an array of pointers to functions. If you need help with complex definitions, see the info on the Right-Left Rule:

http://jdurrett.ba.ttu.edu/3345/handouts/RL-rule.html

I've written the code so you can enter a number that simulates the bit values per jremington's idea. You'll need to expand the example to cope with all 16 values.

Honestly a "big messy set of if statements" is a good way to do this. So long as you put all the actual work into separate functions, you should be able to get all those if statements onto a single page so that they can be grokked together.

So with 4 bits set at 0 or 1 you get 16 possibilities
Does your code have 16 things to deal with, one for each value of the combined bits?

If you have less, then you have more flexibility keeping them separate and doing if statements with && and using else rather than checking the 4 cases that might be similar if , say bit 3 is low, and that needs to trigger something.

MorganS:
Honestly a "big messy set of if statements" is a good way to do this. So long as you put all the actual work into separate functions, you should be able to get all those if statements onto a single page so that they can be grokked together.

I agree it would be a messy set of if statements. Indeed, it should be written as a cascading set of if statement blocks to avoid unnecessary testing. However, even that is inefficient 50% of the time because of having to fall through unnecessary if blocks. The pointer to function approach fits on 3 lines, including error checking. If you have unused bit values, just write a "fall-through" function. For example, say the bit values 3 and 6 are unused:

void fallThrough()
{
   Serial.println("Bogus value");
}

void (*functionPtr[])() = {func0, func1, func2, fallThrough. func4, func5, fallThrough};   // Fill in missing functions later...


// More code as shown in earlier post...

                           // Now the error checking and function invocation...
    if (mockBitValue < 7) {             // Change when you've added more functions
      functionPtr[mockBitValue]();      // Invoke the function
    }

Now there's only 3 lines of code to determine what to do; no cascading if or case statement blocks.

@econjack

Indeed that's a smart approach if what to do is well defined, well separated and it comes at the "cost" of creating a 16 pointer array. if not well separated, lazy programmers would then be tempted to duplicate code in functions.

So before going there I would probably want to have a look at what logic needs to be executed based on the individual values and see how this can be organized. I would be surprised if there are actually 16 very different codes for the 16 different patterns (but might be wrong).

ohgary:
I have a board that has 4 data bits of output. I need to read these 4 bits and based on on the value perfom a task. Without doing a big messy if/else statement base on a digitalRead(pinx) how can I read the digialpins convert the 4 digialpins into a value. Not sure how to combine the bits into a single int value or convert it to a binary/octal value for compairson in an if statement.

Something onlong the lines of this pseudo code.

bit1 = digitalpin1
bit2 = digitalpin2
bit3 = digitalpin3
bit4 = digitalpin4

binvalue = bit1 combined with bit2, with bit3 and bit4

if (binalue = 0110 )
{
do xtask
}

or

bit1 = digitalpin1
bit2 = digitalpin2
bit3 = digitalpin3
bit4 = digitalpin4

iintvalue = bit1 combined with bit2, with bit3 and bit4

if (intvalue == 8 )
{
do xtask
}

Sorry, but you have not told us how the other device tells you all 4 bits are available to be read. Must be more to this story.

Paul

You can use the bitset(x,n) function to establish the value then switch/case to evaluate the results.

econjack:
I agree it would be a messy set of if statements. Indeed, it should be written as a cascading set of if statement blocks to avoid unnecessary testing. However, even that is inefficient 50% of the time because of having to fall through unnecessary if blocks. The pointer to function approach fits on 3 lines, including error checking. If you have unused bit values, just write a "fall-through" function.

Inefficient? A true/false comparison can be executed in a small fraction of a nanosecond. A dozen of them still won't take up a full nanosecond.

It looks like you are trying to create a "jump table." This is a primitive construct that was used extensively in assembler programs. The gcc compiler may create a jump table for you - it often uses this construct for switch() statements. However it is better at it than you. It can make a jump table use the underlying hardware features that you don't know about.

What is important when coding in C or C++ is clarity for the programmer. How can you structure your program in a way that's easy to understand? Can you put all of the logic onto one page so we don't have to scroll up and down to find the definition of the jump array and decode the short function names? (It's difficult to put comments into your jump array.) If you can do that, then the optimizing compiler can consider several different methods that will make equivalent code run faster in less memory space than your hand-made solution.

"A true/false comparison can be executed in a small fraction of a nanosecond."
Well, 62.5nS for a one-clock instruction at 16 MHz.

I think I'd do it as a Port read, then switch:case from there.

jumpLocation = (PIND & 0b0011100)>>2; // look at bits 5-4-3-2
switch(jumpLocation){
case 0b00000000:
// case 0
break;
case 0b00000001:
// case 1
break:
case 0b00000010;
// case 2
break;
case 0b00000011;
// 
break;
// etc up to final case:
case 0b00001111;
// case 15
break;
}
// end switch:case

[Edit - that should have been 0b00111100, missed a 1]

jumpLocation = (PIND & 0b0011100)>>2; // look at bits 5-4-3-2

I think there is a typo in that binary constant.

I think I'd do it as a Port read, then switch:case from there.

+1, and probably the fastest, least code, and understandable.

MorganS:
Inefficient? A true/false comparison can be executed in a small fraction of a nanosecond. A dozen of them still won't take up a full nanosecond.

What is important when coding in C or C++ is clarity for the programmer.

Inefficient to me means there is a way to write the code that: 1) uses less memory, 2) executes faster, 3) is easier to read, or 4) some combination of the three. Saying a dozen logic tests won't take a nanosecond is simply false, as CrossRoads points out. The first two factors mentioned here are, to me, the most important because the third factor is somewhat related to how experienced one is in programming. Improving that factor depends upon how much you want to stretch yourself and learn from using different ways of accomplishing a given task.

Also, I think cascading if statement blocks are more difficult to read than the three statements I used. If there are truly 16 different tasks to be done, a cascading if block with that many tests may force scrolling; another thing I'm not a big fan of doing.

As to the compiler, I'm not trying to "out guess" the compiler. Indeed, I assume that it creates a jump table for switch/case blocks. True, jump tables are venerable structures that have been around forever. However, jump tables have been around for a long time because they are efficient. Saying that some data structure is "old" may not be an indictment against it, but rather proof that it is a viable way to perform the task for which it was designed.

The good news is that there is more than one way to solve a given programming problem, as we have seen here. I like CrossRoads solution better than mine, but prefer mine over a cascading if block because it uses less memory, executes faster, and, for me at least, is easier to read. Everyone is free to choose what works for them. However, exposing the OP to different methods of performing a given task is a good thing, not a bad thing.

Having coded a fair bit using bit manipulation, there seems to be some confusion in this tread.
Newbies should read this Wiki about bit position labels. Arduino is little-endian, with bit 0 to the right.
Labeling the right most Bit one instead of zero is a leading cause of confusion and mistakes almost newbies.

CrossRoads approach is the clearest, no need to convert to hex or decimal, except the mask should be 0b00111100 for bits 5 to 2, and I would have consider a default case for the unexpected.
I probably would have not bothered shifting right 2 bits, and simply use the set bit positions for the case.

"Old" means "no longer relevant to the current programming language. "Goto" is old.

  1. is only relevant if you are running out of memory. If you have enough memory then using all of it could be taken as efficiency.

  2. is only relevant if the program is currently too slow. If its fast enough then stop optimising.

  3. is always relevant, no matter the experience level of the programmer. I would say it is more important for the newbie as guess who is going to be reading the newbie's code a week from now? That same newbie.