ap2357-dev38-nbt26

View the Project on GitHub ece3140-sp2025/ap2357-dev38-nbt26

Embedded Keys

By: David Valarezo, Nathnael Tesfaw, Anthony Paredes-Bautista

Overview:

For our final project we wanted to use our board to simulate a piano. We did this by using the touch slider interface on our board. We divided the touch slider into 5 regions, each corresponding to a different piano key. We used the two buttons on the side of the slider to also represent buttons. The piano notes range from C4 to B4. We used a piezo buzzer to output the correct note depending on what key we’re pressing. We also created logic that would play a special tune if the user holds the two switch buttons down for a few seconds.

System Diagram:

Screenshot 2025-05-14 at 4 21 51 PM

Screenshot 2025-05-14 at 9 42 40 PM

Demo:

Link to our video: https://drive.google.com/file/d/14T9HkcXC2n1E8W-GcuEIgz6YVdRtPcOv/view?usp=sharing

Technical Approach:

Hardware:

  1. Piezo buzzer is connected to PTE 19
  2. Two GPIO buttons (SW1/PTC3 and SW3/PTC12)
  3. Touch Slider Interface

Software Implementation:

Our program is divided into many smaller parts. First we enable the board’s clocks, pins, and UART in main.c. After that, we call TSI_Setup() to power the touch slider interface. We also call calibrateSliderThresholds() to teach the slider what “untouched” looks like. The function measures the average ‘no-touch’ readings from the slider and sets a threshold slightly above that value to detect finger touches. During normal operation, the function ReadSlider() in slider.c is called repeatedly to check for finger touches. Each time it runs, it performs two new TSI scans for the two sensors on either end of the slider. It adds those two values together and compares the result to totalThreshold, which we set earlier during calibration.

If the total is lower than totalThreshold, that means the player’s finger is on the slider. To make sure it’s not noise, our program only accepts the touch after seeing this low value five times in a row. Once we confirm the valid touch we calculate the finger’s position using the following formula: lastPos = (uint16_t)(right * 100U / sum).This formula gives a value from 0 to 100. That position is then used inside piano.c to decide which musical note to play To make sure our notes sound smooth we let all the timing for the buzzer be handled by a hardware timer. When a new note needs to be played, the function startBeepInterrupt(freqHz) in buzzer.c is called by piano.c. This function takes the desired frequency of the note, calculates how many clock ticks are needed for half of the wave cycle, and sets up PIT channel 0 to generate interrupts at exactly that rate.

Every time PIT channel 0 reaches zero, it triggers an interrupt, which is handled by Buzzer_PITHandler(). Inside that interrupt, we simply clear the interrupt flag and toggle the buzzer pin on PTE19. This toggling lets us create our sound by creating a square wave at the correct frequency. Since channel 0 controls the timing, the note sounds smooth even if the processor is doing other things in the background.

A second timer channel, initialized by ButtonHold_Init(), fires every millisecond. Inside its handler, ButtonHold_PITHandler(), we sample SW1 and SW3; if both stay pressed, a counter advances until it hits 5 000, at which point the ISR sets the shared flag intro_requested. The main loop is therefore simple: on each pass it checks that flag—if set, it calls stopBeep() and playAssumptions() to run our hard-coded melody; otherwise, it invokes updateTone(), which reads the latest slider position, translates it to a note, and, when necessary, re-programs PIT channel 0 through startBeepInterrupt().

To implement our special tune we used PIT Channel 1 to detect when both buttons are being held down. We set up this timer using the ButtonHold_Init() function, and we configured it to fire once every millisecond. Each time the timer interrupt runs, the function ButtonHold_PITHandler() checks the state of the two buttons, SW1 and SW3. If both buttons are pressed at the same time, it increases a counter by one. If they stay pressed for 5,000 milliseconds, the counter reaches the limit and the function sets a special flag called intro_requested.

The main loop checks the flag every iteration. If it’s set, our program stops whatever tone is currently playing using stopBeep() and then plays our pre-written tune using playAssumptions(). If the flag isn’t set, our program continues normally by calling updateTone() to read the current slider position, determining which note to play, and calling startBeepInterrupt() with the new note’s frequency.

By modularizing everything, we were able to keep our code simple, separate concerns, and make development easy for all three of us.

Testing and Debugging:

Most of the testing for this project was empirical testing. The ultimate measure of success involving implementation was sensory and sorta subjective. Besides simply building the project in the IDE then flashing it on to the board to see the complex desired behavior, we also used a program called PuTTY to connect to the board and get the slider readings. These readings were vital to ensure that we were splitting up the segments of the board fairly for all keys.

Raw TSI Value Testing:

We created a simple test program that directly read and displayed the raw values from the capacitive touch slider:

Left: 445 Right: 441 Sum: 886 Position: 49% Left: 445 Right: 440 Sum: 885 Position: 49% Left: 445 Right: 440 Sum: 885 Position: 49% Left: 444 Right: 440 Sum: 884 Position: 49%

This test revealed a significant issue: the TSI sensors were reporting consistent values (~886 sum) even when nobody was touching the slider. This “phantom touch” phenomenon is common with capacitive sensors, which can be affected by:

Environmental noise:

Electromagnetic interference from nearby electronics Parasitic capacitance: Inherent capacitance in the PCB traces Humidity and temperature: Environmental factors affecting baseline readings

Implementing Solutions:

To address these issues, we implemented several techniques:

  1. Calibration Routine:

We developed an auto-calibration function that runs at startup to establish a baseline value by sampling the untouched slider multiple times:

cvoid calibrateSliderThresholds(void) {

    uint32_t baselineSum = 0;
    
    for (int i = 0; i < CALIB_SAMPLES; i++) {
    
        // Read both electrodes multiple times
        
        // ...
        
        baselineSum += (uint32_t)leftVal + (uint32_t)rightVal;
        
    }
    
    uint32_t idleBaseline = baselineSum / CALIB_SAMPLES;
    
    totalThreshold = idleBaseline + BASELINE_OFFSET;
    
}

This calibration adds a fixed offset (200 in our case) to the baseline, creating a threshold that distinguishes real touches from background noise.

  1. Hysteresis Implementation:

Our testing showed that even with calibration, the values could fluctuate around the threshold. To prevent rapid on/off switching, we implemented hysteresis:

cif (sum < totalThreshold) {

    if (++missCount >= HYSTERESIS_COUNT) {
    
        lastPos = NO_TOUCH;
        
    }
    
    return lastPos;  // Keep previous position until we've missed enough times
    
}

This approach requires the reading to fall below the threshold for multiple consecutive samples (HYSTERESIS_COUNT = 5) before declaring “no touch,” eliminating brief fluctuations.

  1. Multi-level Debouncing:

Beyond hysteresis at the raw value level, we implemented a secondary stability check at the note level:

cif (note == lastNote) {

    stableCount++;
    
} else {

    lastNote = note;
    
    stableCount = 1;
    
}
if (stableCount >= SLIDER_STABLE_COUNT) {

    return lastNote;  // Only return a note when it's stable
    
} else {

    return '\0';      // Otherwise return "no input"
    
}

This ensured that a note would only be played when the same position was detected for multiple readings (SLIDER_STABLE_COUNT = 3).

Debugging Process:

Our debugging was highly iterative. We would:

Run the raw value test to observe baseline behavior

Adjust the threshold offset and observe its effect

Test with different hysteresis values to find the optimal balance between responsiveness and stability

Validate the final solution by playing the piano with various touch patterns

FULL TEST RESULT:

Screenshot 2025-05-14 201535

Easter Egg Testing:

One cool feature we added to our piano project was a hidden “easter egg” that plays a melody when both buttons are held simultaneously for 2 seconds. Getting this feature to work reliably required some specific testing approaches.

The Button-Hold Test:

To validate our dual-button detection logic without getting distracted by the complexities of the full project, we created a dedicated test program. This approach let us focus exclusively on the button timing and detection mechanism.

c/* Visual feedback of button presses */

test_set_red_led(sw1);

test_set_green_led(sw3);

The test used the board’s LEDs to provide real-time visual feedback - a simple but effective debugging trick. The red LED lit up when SW1 was pressed, and the green LED lit up when SW3 was pressed, making it obvious if there were any button detection issues.

Challenges We Encountered: Getting the timing right was trickier than expected. At first, we had some weird issues where:

Phantom Activations: The easter egg would sometimes trigger without holding both buttons for the full 2 seconds. Missed Activations: Other times, we’d hold both buttons forever and nothing would happen!

The culprit? Our method of checking button states was suffering from a classic timing issue. The PIT (Periodic Interrupt Timer) we were using was correctly counting milliseconds, but we weren’t being careful about resetting the counter properly when buttons were released momentarily. Fixing the Counter Logic The fix required tightening up our counter reset logic:

cif (sw1 && sw3) {

    /* Both buttons pressed */
    
    if (++hold_time >= BUTTON_HOLD_PERIOD_MS) {
    
        /* Easter egg triggered! */
        
    }
    
} else {

    /* Reset counter immediately if either button released */
    
    hold_time = 0;
    
}

This ensured that the counter would reset the moment either button was released, preventing false triggers from partial holds.

The Importance of Visual Feedback:

The blinking LED pattern became our best friend during testing. We deliberately designed a distinctive pattern - something you couldn’t miss or confuse with normal operation:

c/* Final long flash with both LEDs */

test_set_red_led(true);

test_set_green_led(true);

test_delay_ms(1000);

test_set_red_led(false);

test_set_green_led(false);

This made it immediately obvious when our code was working. This saved tons of debugging time - no need to hook up a logic analyzer or set breakpoints; we could just look at the LEDs.

Team Work:

Anthony Paredes-Bautista (Own Computer):

Tackled the capacitive touch slider part of the project, getting it to reliably detect touches and report positions. Made the whole project more organized by breaking it into separate modules with their files and functions. This made it easier for everyone to work on different parts at the same time and keep track of what each piece was supposed to do. His work on the slider gave us the foundation for turning touches into musical notes.

David Valarezo & Nathnael Tesfaw (Peer Programming):

Worked together on making the buzzer produce musical sounds, figuring out how to map the slider positions to different notes. They added the ability to play notes using the physical buttons (SW1 and SW3) and came up with the fun “secret feature” where holding both buttons for 2 seconds plays a little melody. They spent a lot of time testing different input combinations to ensure everything worked smoothly together, and the piano felt responsive when playing notes.

We met up regularly to share progress and help each other solve problems. Everyone contributed ideas for improvements and helped with testing. The final project came together nicely with the slider detecting touches, the code figuring out which note to play, and the buzzer making the sounds – all working together as a simple but fun musical instrument.

Outside Resources:

NXP FRDM-KL46Z Reference Manual (TSI & PIT chapters) https://forum.digikey.com/t/using-the-capacitive-touch-sensor-on-the-frdm-kl46z/13246 (Capacitive Touch Sensor) https://www.planetanalog.com/the-art-of-capacitive-touch-sensing/%20(Capacitive Touch Sensor) https://www.planetanalog.com/the-art-of-capacitive-touch-sensing/ (Capacitive Touch Sensor MORE)