Keypad Electronic Piano

Keypad Electronic Piano

HARDWARE REQUIRED:

  • PICUNO Microcontroller board
  • 1 × 4x4 Button Matrix Module
  • 1 × I2C LCD Display
  • 1 × 4xAA Battery Pack (For external supply)
  • 1 × Buzzer
  • Jumper wires
  • USB cable

DESCRIPTION:

This project turns the 4x4 keypad into a simple musical instrument. Each of the 16 buttons is mapped to a specific musical note in a higher octave for a clearer sound. When a key is pressed, its note is played on the buzzer, and the name of the note (e.g., "C5", "G#5") is displayed on the LCD screen, providing both audible and visual feedback.

CIRCUIT DIAGRAM:

Keypad Electronic Piano
  • Connect the LCD Module's GND pin to GND.
  • Connect the LCD Module's VCC pin to the Positive (+) terminal of the 4xAA Battery Pack.
  • Connect the LCD Module's SDA pin GPIO 4 (SDA Pin on PICUNO).
  • Connect the LCD Module's SCL pin GPIO 5 (SCL Pin on PICUNO).
  • Connect the negative terminal of the 4xAA Battery Pack to Common GND on breadboard.
  • 4x4 Button Matrix Module:
    • Connect the Row pins (5-8) to GPIO 6, 7, 8 and 9 respectively.
    • Connect the Column pins (1-4) to GPIO 10, 11, 12 and 13 respectively.
  • Buzzer Module:
    • Connect the GND (-) pin to GND pin.
    • Connect the VCC (+) pin to 5V.
    • Connect the Signal (S) pin to GPIO 16.

    SCHEMATIC:

    I2C LCD Display:

    LCD VCC → 4xAA Battery Pack (+)

    LCD GND → GND

    LCD SDA → GPIO 4 (Board SDA Pin)

    LCD SCL → GPIO 5 (Board SCL Pin)

    4x4 Button Matrix Module:

    Pin 1 (Column 4) → GPIO 13

    Pin 2 (Column 3) → GPIO 12

    Pin 3 (Column 2) → GPIO 11

    Pin 4 (Column 1) → GPIO 10

    Pin 5 (Row 1) → GPIO 9

    Pin 6 (Row 2) → GPIO 8

    Pin 7 (Row 3) → GPIO 7

    Pin 8 (Row 4) → GPIO 6

    Buzzer Module:

    VCC / (+) → 5V

    GND / (-) → GND

    Signal (S) → GPIO 16

    Common Ground Connection:

    4xAA Battery Pack (-) → PICUNO Board GND

    CODE -- C:

    #include <Wire.h>
    #include <LiquidCrystal_I2C.h>

    const int BUZZER_PIN = 16;

    // --- Keypad Setup ---
    const byte ROWS = 4;
    const byte COLS = 4;
    char keys[ROWS][COLS] = {
      {'1','2','3','A'},
      {'4','5','6','B'},
      {'7','8','9','C'},
      {'*','0','#','D'}
    };
    byte rowPins[ROWS] = {6, 7, 8, 9};
    byte colPins[COLS] = {10, 11, 12, 13};

    LiquidCrystal_I2C lcd(0x27, 16, 2);

    // --- Note Frequencies in a Higher Octave (C5 to D#6) ---
    const int NOTE_C5 = 523; const int NOTE_CS5 = 554; const int NOTE_D5 = 587; const int NOTE_DS5 = 622;
    const int NOTE_E5 = 659; const int NOTE_F5 = 698; const int NOTE_FS5 = 740; const int NOTE_G5 = 784;
    const int NOTE_GS5 = 831; const int NOTE_A5 = 880; const int NOTE_AS5 = 932; const int NOTE_B5 = 988;
    const int NOTE_C6 = 1047; const int NOTE_CS6 = 1109; const int NOTE_D6 = 1175; const int NOTE_DS6 = 1245;

    void setup() {
      pinMode(BUZZER_PIN, OUTPUT);
      
      for (byte r = 0; r < ROWS; r++) {
        pinMode(rowPins[r], OUTPUT);
        digitalWrite(rowPins[r], HIGH);
      }
      for (byte c = 0; c < COLS; c++) {
        pinMode(colPins[c], INPUT_PULLUP);
      }
      
      lcd.init();
      lcd.backlight();
      lcd.setCursor(1, 0);
      lcd.print("Keypad Piano");
    }

    void loop() {
      char key = get_key();

      if (key) {
        playNote(key);
      }
    }

    // --- Helper Functions ---

    void playNote(char key) {
      lcd.clear();
      lcd.setCursor(0,0);
      lcd.print("Playing Note:");
      lcd.setCursor(0,1);
      int noteDuration = 150; // milliseconds

      switch (key) {
        // First Row
        case '1': tone(BUZZER_PIN, NOTE_C5, noteDuration); lcd.print("C5"); break;
        case '2': tone(BUZZER_PIN, NOTE_CS5, noteDuration); lcd.print("C#5"); break;
        case '3': tone(BUZZER_PIN, NOTE_D5, noteDuration); lcd.print("D5"); break;
        case 'A': tone(BUZZER_PIN, NOTE_DS5, noteDuration); lcd.print("D#5"); break;
        // Second Row
        case '4': tone(BUZZER_PIN, NOTE_E5, noteDuration); lcd.print("E5"); break;
        case '5': tone(BUZZER_PIN, NOTE_F5, noteDuration); lcd.print("F5"); break;
        case '6': tone(BUZZER_PIN, NOTE_FS5, noteDuration); lcd.print("F#5"); break;
        case 'B': tone(BUZZER_PIN, NOTE_G5, noteDuration); lcd.print("G5"); break;
        // Third Row
        case '7': tone(BUZZER_PIN, NOTE_GS5, noteDuration); lcd.print("G#5"); break;
        case '8': tone(BUZZER_PIN, NOTE_A5, noteDuration); lcd.print("A5"); break;
        case '9': tone(BUZZER_PIN, NOTE_AS5, noteDuration); lcd.print("A#5"); break;
        case 'C': tone(BUZZER_PIN, NOTE_B5, noteDuration); lcd.print("B5"); break;
        // Fourth Row
        case '*': tone(BUZZER_PIN, NOTE_C6, noteDuration); lcd.print("C6"); break;
        case '0': tone(BUZZER_PIN, NOTE_CS6, noteDuration); lcd.print("C#6"); break;
        case '#': tone(BUZZER_PIN, NOTE_D6, noteDuration); lcd.print("D6"); break;
        case 'D': tone(BUZZER_PIN, NOTE_DS6, noteDuration); lcd.print("D#6"); break;
      }
    }

    char get_key() {
      for (byte r = 0; r < ROWS; r++) {
        digitalWrite(rowPins[r], LOW);
        for (byte c = 0; c < COLS; c++) {
          if (digitalRead(colPins[c]) == LOW) {
            delay(50);
            if (digitalRead(colPins[c]) == LOW) {
              while (digitalRead(colPins[c]) == LOW) {}
              digitalWrite(rowPins[r], HIGH);
              return keys[r][c];
            }
          }
        }
        digitalWrite(rowPins[r], HIGH);
      }
      return '\\0';
    }
    Note Frequencies - Constants that store the specific frequencies (in Hertz) for each musical note, making the code readable.

    get_key() function - Manually scans the keypad's rows and columns to detect a single, complete button press without needing an external library.

    pinMode(..., INPUT_PULLUP) - Activates an internal resistor on the Arduino to keep the column pins stable and allow for reliable key press detection.

    Key Release Logic (while loop) - After a key press is detected, this loop waits for the button to be physically released, ensuring one press results in only one note.

    switch (key) statement - An efficient way to check which key was pressed and then run the specific code to play the correct note and display its name.

    tone(pin, frequency, duration) - Generates a sound wave on the buzzer pin at a specific frequency for a set duration, allowing it to play musical notes.

    CODE -- PYTHON:

    from machine import Pin, PWM, I2C
    from time import sleep_ms

    from i2c_lcd import I2cLcd

    BUZZER_PIN = 16

    # --- Keypad Setup ---
    keypad_map = [
        ['1', '2', '3', 'A'],
        ['4', '5', '6', 'B'],
        ['7', '8', '9', 'C'],
        ['*', '0', '#', 'D']
    ]
    row_pins = [Pin(pin, Pin.OUT) for pin in [6, 7, 8, 9]]
    col_pins = [Pin(pin, Pin.IN, Pin.PULL_DOWN) for pin in [10, 11, 12, 13]]

    I2C_ADDR = 0x27
    i2c = I2C(0, scl=Pin(5), sda=Pin(4))
    lcd = I2cLcd(i2c, I2C_ADDR, 2, 16)

    buzzer = PWM(Pin(BUZZER_PIN))

    notes = {
        '1': ("C5 ", 523), '2': ("C#5", 554), '3': ("D5 ", 587), 'A': ("D#5", 622),
        '4': ("E5 ", 659), '5': ("F5 ", 698), '6': ("F#5", 740), 'B': ("G5 ", 784),
        '7': ("G#5", 831), '8': ("A5 ", 880), '9': ("A#5", 932), 'C': ("B5 ", 988),
        '*': ("C6 ", 1047),'0': ("C#6", 1109),'#': ("D6 ", 1175),'D': ("D#6", 1245)
    }

    # --- Helper Functions ---
    def play_tone(freq, duration_ms):
        if freq > 0:
            buzzer.freq(freq)
            buzzer.duty_u16(32768)
        sleep_ms(duration_ms)
        buzzer.duty_u16(0) # Turn buzzer off

    def read_keypad():
        for r, row_pin in enumerate(row_pins):
            row_pin.high()
            for c, col_pin in enumerate(col_pins):
                if col_pin.value() == 1:
                    sleep_ms(50)
                    if col_pin.value() == 1:
                        while col_pin.value() == 1: pass
                        row_pin.low()
                        return keypad_map[r][c]
            row_pin.low()
        return None

    # --- Main Program ---
    lcd.putstr("Keypad Piano")

    while True:
        key = read_keypad()
        if key:
            if key in notes:
                # Look up the note name and frequency from the dictionary
                note_name, frequency = notes[key]

                # Display the note on the LCD
                lcd.clear()
                lcd.putstr("Note:")
                lcd.move_to(0, 1)
                lcd.putstr(note_name)

                # Play the note on the buzzer
                play_tone(frequency, 200) # Play for 200ms
    notes dictionary - This is the core of the project. A Python dictionary is used to cleanly map each key character to its corresponding Note Name and Frequency. This is much neater than a long if/elif/else chain.

    Higher Octave - The frequencies defined in the notes dictionary are for the 5th and 6th octaves (e.g., C5, C6). This makes the notes higher-pitched, which our ears perceive as louder and clearer on a small buzzer.

    play_tone() function - This helper function takes a frequency and duration, making it easy to play any note. It uses PWM to generate the correct sound wave.

    if key in notes: - This line efficiently checks if the pressed key exists as a key in our notes dictionary before trying to access it, preventing errors.

    note_name, frequency = notes[key] - This is a clean way to unpack the data (the tuple) associated with a key from the dictionary into two separate variables.