4-Bit Binary Counter Using Multi-Function Shield

4-Bit Binary Counter Using Multi-Function Shield

HARDWARE REQUIRED:

  • PICUNO Microcontroller board
  • 1 × Multi-Function Shield
  • USB cable

DESCRIPTION:

This project demonstrates the use of a Multi-function Shield, which is a pre-built circuit board that contains common components like LEDs, buttons, potentiometer, buzzer and shift registers already wired to specific pins. Using a shield like this simplifies the hardware setup by eliminating the need for a breadboard and individual jumper wires, allowing you to focus directly on the programming logic.

The program turns the four onboard LEDs of the shield into a 4-bit binary display. The user can control a number from 0 (binary 0000) to 15 (binary 1111) using the three push buttons. One button increases the count, another decreases it, and the third resets it to zero.
  • Plug the Multi-function Shield directly on top of the PICUNO board. The shield's components have fixed connections to the board's pins.
  • LEDs (D1-D4) are connected to GPIO 13, 12, 11, and 10.
  • Buttons (S1-S3) are connected to Analog Pins A1, A2, and A3.

SCHEMATIC:

No external wiring is required. Place the Multi-Function shield directly on top of the PICUNO board.

Multi-Function shield:

LEDs (D1-D4) → GPIO 13, 12, 11, 10.

Buttons (S1-S3) → A1, A2, A3.

CODE -- C:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
// LEDs (D1, D2, D3, D4 on shield)
const int LED_1_PIN = 13; 
const int LED_2_PIN = 12;
const int LED_3_PIN = 11;
const int LED_4_PIN = 10; 
const int ledPins[] = {LED_1_PIN, LED_2_PIN, LED_3_PIN, LED_4_PIN};

// Buttons (S1, S2, S3 on shield are on Analog Pins A1, A2, A3)
const int BUTTON_INC_PIN = A1;
const int BUTTON_DEC_PIN = A2;
const int BUTTON_RST_PIN = A3;

// --- Global Variables ---
int count = 0;
unsigned long lastDebounceTime = 0;
const long debounceDelay = 200; // 200 milliseconds

// --- Helper Function ---
void displayBinary(int number) {
  for (int i = 0; i < 4; i++) {
    if ((number >> i) & 1) {
      digitalWrite(ledPins[i], LOW); 
    } else {
      digitalWrite(ledPins[i], HIGH); 
    }
  }
}

void setup() {
  for (int i = 0; i < 4; i++) {
    pinMode(ledPins[i], OUTPUT);
    digitalWrite(ledPins[i], HIGH); 
  }
  pinMode(BUTTON_INC_PIN, INPUT_PULLUP);
  pinMode(BUTTON_DEC_PIN, INPUT_PULLUP);
  pinMode(BUTTON_RST_PIN, INPUT_PULLUP);

  Serial.begin(9600);
  Serial.println("4-Bit Binary Counter Ready.");
  displayBinary(count);
}

void loop() {
  if ((millis() - lastDebounceTime) > debounceDelay) {
    
    // --- Check Increment Button ---
    if (digitalRead(BUTTON_INC_PIN) == LOW) {
      count++;
      if (count > 15) count = 0;
      displayBinary(count);
      Serial.print("Count: "); Serial.println(count);
      lastDebounceTime = millis(); // Reset the timer
    }
    
    // --- Check Decrement Button ---
    else if (digitalRead(BUTTON_DEC_PIN) == LOW) {
      count--;
      if (count < 0) count = 15;
      displayBinary(count);
      Serial.print("Count: "); Serial.println(count);
      lastDebounceTime = millis(); 
    }

    // --- Check Reset Button ---
    else if (digitalRead(BUTTON_RST_PIN) == LOW) {
      count = 0;
      displayBinary(count);
      Serial.println("Count RESET");
      lastDebounceTime = millis(); 
    }
  }
}
displayBinary() function - This is the core of the visual output. It takes a number (from 0 to 15) and uses bitwise operations (>> and &) to check each of the four bits. It then sets the corresponding LED pin LOW (on) or HIGH (off) to display the number in binary.

INPUT_PULLUP - This command in setup() configures the button pins as inputs and activates a small internal resistor that keeps their signal stable. This is why the code checks for a LOW signal to detect a press.

millis() Timer - This is the key to the debouncing fix. The millis() function returns the number of milliseconds since the board started. It's used to create a non-blocking timer.

Debounce Logic - The line if ((millis() - lastDebounceTime) > debounceDelay) is the most important part of the fix. It ensures the code only listens for a new button press if 200 milliseconds have passed since the last valid press was registered.

CODE -- PYTHON:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
from machine import Pin
from time import sleep_ms

# LEDs (D1, D2, D3, D4 on shield)
LED_1 = Pin(13, Pin.OUT, value=1) 
LED_2 = Pin(12, Pin.OUT, value=1) 
LED_3 = Pin(11, Pin.OUT, value=1) 
LED_4 = Pin(10, Pin.OUT, value=1) 
leds = [LED_1, LED_2, LED_3, LED_4]

# Buttons (S1, S2, S3 on shield)
BUTTON_INC = Pin(27, Pin.IN, Pin.PULL_UP) 
BUTTON_DEC = Pin(28, Pin.IN, Pin.PULL_UP) 
BUTTON_RST = Pin(29, Pin.IN, Pin.PULL_UP) 

count = 0
# Variables to track button states for a single press detection
last_inc_state = BUTTON_INC.value()
last_dec_state = BUTTON_DEC.value()
last_rst_state = BUTTON_RST.value()

# --- Helper Function ---
def display_binary(number):
    """Displays a number (0-15) in binary on the 4 LEDs."""
    for i in range(4):
        if (number >> i) & 1:
            leds[i].low()  
        else:
            leds[i].high() 

# --- Main Program ---
print("4-Bit Binary Counter Ready.")
print("Press S1 to increment, S2 to decrement, S3 to reset.")
display_binary(count) 

while True:
    # --- Check Increment Button ---
    inc_state = BUTTON_INC.value()
    if inc_state == 0 and last_inc_state == 1: 
        count += 1
        if count > 15: 
            count = 0
        display_binary(count)
        print(f"Count: {count}")
    last_inc_state = inc_state

    # --- Check Decrement Button ---
    dec_state = BUTTON_DEC.value()
    if dec_state == 0 and last_dec_state == 1:
        count -= 1
        if count < 0: 
            count = 15
        display_binary(count)
        print(f"Count: {count}")
    last_dec_state = dec_state

    # --- Check Reset Button ---
    rst_state = BUTTON_RST.value()
    if rst_state == 0 and last_rst_state == 1:
        count = 0
        display_binary(count)
        print("Count RESET")
    last_rst_state = rst_state
    
    sleep_ms(20)
Pin(..., Pin.IN, Pin.PULL_UP) - Configures the button pins as inputs and activates internal pull-up resistors, which keeps the signal stable. The code then looks for a 0 to detect a press.

display_binary() function - This is the core logic. It takes a number (0-15) and uses bitwise operations (>> and &) to check each of the four bits. It then turns the corresponding LED on or off.

Active Low LEDs - On this shield, the LEDs turn ON when the pin is set to low(). The display_binary function is written to handle this.

State Change Detection - The logic if inc_state == 0 and last_inc_state == 1: is used for each button to detect a "rising edge." This ensures that one long button press only registers as a single event.