Currently you are just setting the LEDs manually one after another in your code. That is not actually a binary counter (as it is most likely wanted by the exercise), but only looks like one.
First you need to understand, what a counter really is: Basically it is just a variable, that holds a number. Where exactly this variable is saved, is not important. For your case you would use a variable in your code. The Counter/Timer hardware in the Arduino uses a fixed SFR (Special Function Register) for it.
That the counter is 4bit, means, that its value is hold by only 4 bits. That gives you a value range from 0 to 15 (2^4
values). So a 4bit counter variable would count from 0 to 15. You could just have a counter variable, that you could increment or decrement (depending on the direction, that you want to count).
// Declare counter variable and initialize with zero
int counter_variable = 0;
// Increment
counter_variable++;
// or Decrement
counter_variable--;
For the "binary" part: Here it is important to understand, that the decimal number from 0 to 15 is already saved in the variable as binary (since microcontrollers/computers only know binary, nothing else). So you can write out the state of the 4 least significant bytes of the counter variable:
digitalWrite(pin8 ,counter_variable & 0b0001);
digitalWrite(pin9 ,counter_variable & 0b0010);
digitalWrite(pin10 ,counter_variable & 0b0100);
digitalWrite(pin11 ,counter_variable & 0b1000);
The &
is a bitwise AND. The result of the operation will only leave bits set, when they are set on both sides. So here we are isolating the corresponding bits. digitalWrite()
will interpret anything but zero as HIGH. Thus the pin is set HIGH, if the corresponding bit is set in counter_variable
. But you see, that we are repeating us here. We could use a for loop and define our pins in an array:
// at global scope
int led_pins[4] = {8, 9, 10, 11};
// in the button if statement
for(int i=0; i<4; i++){
digitalWrite(led_pins[i], counter_variable & (1 << i));
}
We have organized the pins of the LEDs in an array, so they can be easily indexed. Then we are looping through the pins array and setting the pins similar to the last version. But now we are using (1 << i)
. The <<
operator shifts the bits of the number before it by the number of digits, that comes after it. Decimal 1
is binary 0b0001
. So, when i
is 2 (for example), this would shift the bits 2 digits to the left, giving you 0b0100
. We are just generating the same bit patterns as in the last version.
But we can even optimize further via direct port manipulation. Inside the microcontroller the output pins are controlled via simple Special Function Registers. They work somewhat like a variable. All output pins are organized into socalled "Ports" (groups of up to 8 pins). Each port has one PORTx
registers (where x
stands for the character associated with that port), which sets the state of the pins, if they are in output mode. Setting the corresponding bit in the correct PORTx
register will set the pin to HIGH. Clearing the bit (setting to zero) will set the pin to LOW.
You are using the Arduino pins 8 to 11. If you look at a pinout diagram of the Arduino Uno, you will see, that these pins are also marked as PB0
to PB3
, which stands for "Port B, pins 0 to 3 (inside that port)". The corresponding PORTx
register is PORTB
. So we can set all pins of the port at once by assigning a new value to that register. The following code would set an alternating on/off pattern for all 8 pins of PORTB:
PORTB = 0b10101010;
Now we only want to use the lowest 4 bits and set them to the values, that our counter variable has. So we are masking away the 4 higher bits with the bitwise AND operator and then assign that value to the register:
PORTB = counter_variable & 0b00001111;
So now the if statement in void loop()
might look like this (using a for loop):
if(buttonState == HIGH){
// count up counter variable
for(int counter_variable=0; counter_variable < 16; counter_variable++){
// write out to the pins
PORTB = counter_variable & 0b00001111;
// delay a bit to show the value
delay(timer);
}
}
And now we want that each consecutive button to count in the other direction than before. So we can declare another variable as direction and then use another if statement, to switch between two for loops:
// On global scope
int direction = 1;
// in void loop()
if(buttonState == HIGH){
if(direction == 1){
// count up counter variable
for(int counter_variable=0; counter_variable < 16; counter_variable++){
// write out to the pins
PORTB = counter_variable & 0b00001111;
// delay a bit to show the value
delay(timer);
}
// change direction
direction = -1;
} else {
// count up counter variable
for(int counter_variable=15; counter_variable >= 0; counter_variable--){
// write out to the pins
PORTB = counter_variable & 0b00001111;
// delay a bit to show the value
delay(timer);
}
// change direction
direction = 1;
}
}
Note:
- This is probably not the most efficient way, but I think it is quite understandable (which is also important).
- This still uses much
delay()
calls, just as your code. That will make your button unresponsive. The counter must first finish, before the button will react again. That might be ok for you. If not, you need to restructure the program for using a non-blocking coding style like in the BlinkWithoutDelay
example from the Arduino IDE (using millis()
). There are lots of tutorials about that on the web.
- When using a non-blocking coding style, you also need to check the button for the transition to HIGH, instead for the state high. Otherwise the code would run only, while the button is pressed.
- Buttons bounce, when being pressed. So there are many transitions between HIGH and LOW for a few ms, when the button is pressed. In the code above this is not important, because the started counter needs so long, that every bounce is finished after the counter finished. With code, that needs less than a few ms, you might get into problems there. I would suggest the
Bounce2
library for that. It handles the bounce detection for you.
if(buttonState == HIGH)
means "as long as the button is pressed". You probably want to use a flag that changes, when the button is clicked.