Close
0%
0%

HitPad

Portable Tiny-Music Maker based around M5ATOM and a custom enclosure.

Similar projects worth following
Hey everyone, welcome back! Meet HitPad—a mini music gadget built for pure fun. It’s a portable tone generator that fits in your pocket and lets you tap out beats anytime, anywhere. Whether you're passing time, experimenting with sound, or just enjoying the visuals, HitPad gives you a simple, satisfying way to play. No apps, no setup—just press a button and make some noise. It’s my take on a tiny synth-style device that’s always ready to jam. Powered by the M5Stack AtomS3, featuring a custom-designed PCB and a fully 3D-printed enclosure, HitPad was built entirely from scratch.

M5 STACK ATOM S3

At the heart of HitPad is the M5Stack AtomS3, a compact yet powerful development board built around the ESP32-S3 microcontroller. The ESP32-S3 features a dual-core Xtensa LX7 processor running at up to 240 MHz, with built-in support for Wi-Fi and Bluetooth 5.0, making it ideal for fast, responsive embedded applications.

Despite its tiny 24 × 24 mm footprint, the AtomS3 offers a rich set of features that make it perfect for compact DIY builds. It includes multiple GPIO pins, a programmable RGB LED, a USB-C port for power and programming, and a Grove connector for easy expansion. The board also supports low-power operation, which is useful for portable or battery-powered devices.

As for sourcing the M5STACK ATOM S3 we use in our project, we got it from PCBWAY's Giftshop.

https://pcbway.com/s/2A018d

DESIGN

To begin designing our handheld wearable device, we imported the 3D model of the M5Stack AtomS3 into Fusion360. Since a speaker model wasn’t available, we created one manually along with a custom switch PCB featuring four push buttons.

We then built a simple box-style enclosure around the display area. The lower portion houses the switch PCB, where we added four custom switch actuators labeled A, B, C, and D to match the quiz-style interface. To secure the switch PCB, we included two screw bosses with 1.8 mm holes, sized perfectly for M2 screws.

To join the front and rear halves of the enclosure, we added four M2.5 holes and planned to use M2.5 Torx screws for a clean, secure fit. The overall enclosure was sized to comfortably fit in a child’s hand, making it easy to hold and interact with.

We also added a circular mount on the back, designed to attach an ID card strap, allowing the device to be worn around the neck like a badge. For the final print, the buttons were done in orange PLA, while the main body was printed in white PLA using our Ender K10 Max with tree supports for clean overhangs.

CUSTOM SWITCH PCB

The circuit design for this project was fairly straightforward. The goal was to create a switch PCB featuring four 4×4 tactile push buttons. We began by adding the buttons to the schematic and connecting all four of their pins together, forming a common ground. To simplify wiring, we introduced a CON5 connector, where all button pin 1 terminals were linked along with the shared ground connection.

For the PCB layout, we used the enclosure’s CAD model to define the board outline. The push buttons were placed in their designated positions, and traces were routed from each switch to the CON5 port.

Once the pcb layout was complete, we added custom graphics to the silkscreen layer to enhance the visual appeal of the board and give it a more polished, finished look

PCBWAY SERVICE

Once the board design was finalized, I opted for a purple solder mask with white silkscreen and uploaded the Gerber files to PCBWay’s quote page for fabrication.

While I typically go with a white or black solder mask for most of my builds, this time I decided to try out PCBWay’s Purple option just for a change. The order was placed smoothly, and the PCBs arrived within a week.

The quality was excellent—clean finish, sharp silkscreen, and everything matched the design perfectly.

Over the past ten years, PCBWay has distinguished themselves by providing outstanding PCB manufacturing and assembly services, becoming a trusted partner for countless engineers and designers worldwide.

Also, PCBWay is organizing the PCBWay 8th Project Design Contest, a global event that invites makers, engineers, and innovators to showcase their most creative builds. With categories in Electronics, Mechanical, and AIoT, it’s a great opportunity to share your work, connect with the community, and compete for exciting prizes.

You guys can check out PCBWAY if you want great PCB service at an affordable rate.

M5 v3.step

step - 2.55 MB - 10/06/2025 at 18:49

Download

Sw.3mf

3mf - 236.27 kB - 10/06/2025 at 18:49

Download

UPPER.3mf

3mf - 113.58 kB - 10/06/2025 at 18:49

Download

LOWER.3mf

3mf - 23.25 kB - 10/06/2025 at 18:49

Download

  • 1
    SWITCH PCB ASSEMBLY
    • The switch PCB assembly process starts by placing all the 4x4 through-hole push buttons in their place, then flipping the board over, and then using a soldering iron to secure all components by soldering their pads.
    • Next, we added five connecting wires with the CON5 terminal on the backside of the Switch PCB.
  • 2
    WIRING PROCESS
    • We began the wiring process by adding a CON4 header to the back of the M5Stack AtomS3’s pin header. This connector serves as the interface between the switch PCB and the Atom module.
    • Each of the four push button outputs—A, B, C, and D—was carefully soldered to G5, G6, G7, and G8 on the Atom, respectively. The ground pin from the switch PCB was then connected to the GND pin of the Atom to complete the input circuit.
    • Next, we wired the speaker, connecting its positive lead to G2 and its negative lead to GND, enabling PWM-based audio output.
    • Finally, we connected the lithium cell, which powers the entire device. The positive terminal of the battery was soldered to the 5V pin on the Atom, while the negative terminal was connected to GND, completing the power circuit and making the device fully portable.
  • 3
    CODE

    Here's the code we used in this project

    #include <Arduino.h>
    #include <M5CoreS3.h>
    #include <driver/ledc.h>
    #include <math.h>
    #include <stdlib.h>
    // -----------------------------
    // Pin Assignments
    // -----------------------------
    #define BUTTON_A 8   // Kick
    #define BUTTON_B 7   // Snare
    #define BUTTON_C 6   // Hi-Hat
    #define BUTTON_D 5   // Clap
    #define SPEAKER_PIN 2  // GPIO 2 for audio
    // -----------------------------
    // LEDC (PWM audio) Config
    // -----------------------------
    #define SPEAKER_CHANNEL   LEDC_CHANNEL_0
    #define SPEAKER_TIMER     LEDC_TIMER_0
    #define SPEAKER_RES       LEDC_TIMER_8_BIT
    #define BASE_DUTY 128
    // -----------------------------
    // Waveform Variables
    // -----------------------------
    float phase = 0;             // horizontal scroll offset
    float waveAmplitude = 5;     // current amplitude
    float waveFrequency = 2.0;   // base visual frequency
    float targetAmplitude = 5;   // amplitude target
    float amplitudeDecay = 0.95; // decay factor per frame
    // -----------------------------
    // Speaker Setup
    // -----------------------------
    void setupSpeaker() {
    ledc_timer_config_t ledc_timer = {
    .speed_mode       = LEDC_LOW_SPEED_MODE,
    .duty_resolution  = SPEAKER_RES,
    .timer_num        = SPEAKER_TIMER,
    .freq_hz          = 2000,
    .clk_cfg          = LEDC_AUTO_CLK
    };
    ledc_timer_config(&ledc_timer);
    ledc_channel_config_t ledc_channel = {
    .gpio_num       = SPEAKER_PIN,
    .speed_mode     = LEDC_LOW_SPEED_MODE,
    .channel        = SPEAKER_CHANNEL,
    .intr_type      = LEDC_INTR_DISABLE,
    .timer_sel      = SPEAKER_TIMER,
    .duty           = 0,
    .hpoint         = 0
    };
    ledc_channel_config(&ledc_channel);
    }
    // -----------------------------
    // Drum Sounds
    // -----------------------------
    void playKick() {
    for (int f = 150; f > 50; f -= 5) {
    ledc_set_freq(LEDC_LOW_SPEED_MODE, SPEAKER_TIMER, f);
    ledc_set_duty(LEDC_LOW_SPEED_MODE, SPEAKER_CHANNEL, BASE_DUTY);
    ledc_update_duty(LEDC_LOW_SPEED_MODE, SPEAKER_CHANNEL);
    delay(5);
    }
    ledc_set_duty(LEDC_LOW_SPEED_MODE, SPEAKER_CHANNEL, 0);
    ledc_update_duty(LEDC_LOW_SPEED_MODE, SPEAKER_CHANNEL);
    }
    void playNoise(int duration_ms) {
    unsigned long start = millis();
    while (millis() - start < duration_ms) {
    int val = random(0, 256);
    ledc_set_duty(LEDC_LOW_SPEED_MODE, SPEAKER_CHANNEL, val);
    ledc_update_duty(LEDC_LOW_SPEED_MODE, SPEAKER_CHANNEL);
    delayMicroseconds(50);
    }
    ledc_set_duty(LEDC_LOW_SPEED_MODE, SPEAKER_CHANNEL, 0);
    ledc_update_duty(LEDC_LOW_SPEED_MODE, SPEAKER_CHANNEL);
    }
    void playSnare() { playNoise(120); }
    void playHiHat() { playNoise(50); }
    void playClap() {
    playNoise(80);
    ledc_set_freq(LEDC_LOW_SPEED_MODE, SPEAKER_TIMER, 600);
    ledc_set_duty(LEDC_LOW_SPEED_MODE, SPEAKER_CHANNEL, BASE_DUTY);
    ledc_update_duty(LEDC_LOW_SPEED_MODE, SPEAKER_CHANNEL);
    delay(50);
    ledc_set_duty(LEDC_LOW_SPEED_MODE, SPEAKER_CHANNEL, 0);
    ledc_update_duty(LEDC_LOW_SPEED_MODE, SPEAKER_CHANNEL);
    }
    // -----------------------------
    // Waveform Visualizer
    // -----------------------------
    void drawScrollingWave() {
    CoreS3.Display.fillScreen(TFT_BLACK);
    int width = CoreS3.Display.width();
    int height = CoreS3.Display.height();
    int midY = height / 2;
    for (int x = 0; x < width; x++) {
    int y = midY + waveAmplitude * sin((x * waveFrequency * PI / width) + phase);
    CoreS3.Display.drawPixel(x, y, TFT_WHITE);
    }
    phase += 0.2; // scroll wave horizontally
    waveAmplitude = waveAmplitude * amplitudeDecay + targetAmplitude * (1.0 - amplitudeDecay);
    }
    // -----------------------------
    // Handle Button Presses
    // -----------------------------
    void handleButtonPresses() {
    if (!digitalRead(BUTTON_A)) { // Kick
    playKick();
    targetAmplitude += 15;
    waveFrequency = 3.0;
    }
    if (!digitalRead(BUTTON_B)) { // Snare
    playSnare();
    targetAmplitude += 10;
    waveFrequency = 5.0;
    }
    if (!digitalRead(BUTTON_C)) { // Hi-Hat
    playHiHat();
    targetAmplitude += 8;
    waveFrequency = 7.0;
    }
    if (!digitalRead(BUTTON_D)) { // Clap
    playClap();
    targetAmplitude += 12;
    waveFrequency = 4.0;
    }
    if (targetAmplitude > 30) targetAmplitude = 30; // cap amplitude
    }
    // -----------------------------
    // Setup
    // -----------------------------
    void setup() {
    auto cfg = M5.config();
    CoreS3.begin(cfg);
    setupSpeaker();
    pinMode(BUTTON_A, INPUT_PULLUP);
    pinMode(BUTTON_B, INPUT_PULLUP);
    pinMode(BUTTON_C, INPUT_PULLUP);
    pinMode(BUTTON_D, INPUT_PULLUP);
    CoreS3.Display.setTextSize(2);
    CoreS3.Display.setTextColor(TFT_WHITE, TFT_BLACK);
    CoreS3.Display.setTextDatum(middle_center);
    CoreS3.Display.fillScreen(TFT_BLACK);
    CoreS3.Display.drawString("Drum Pad Ready", CoreS3.Display.width()/2, CoreS3.Display.height()/2);
    delay(1000);
    }
    // -----------------------------
    // Main Loop
    // -----------------------------
    void loop() {
    handleButtonPresses();
    drawScrollingWave();
    delay(30); // adjust speed of wave
    }

View all 4 instructions

Enjoy this project?

Share

Discussions

Does this project spark your interest?

Become a member to follow this project and never miss any updates