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.
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.
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.
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.