Kitchen Timer with a Twist =========================== .. image:: images/timer.jpg :alt: Kitchen Timer This project introduces a unique twist to the conventional kitchen timer. While seemingly simple, it proves to be quite practical. Hardware -------- This project is based on the versatile Swiss-army PCB, with some components omitted for simplicity. Components excluded: - micro SD card, - microphone preamplifier - microphone/headphone sockets (bypassed by a wire link). Components retained: - OLED Display - Buttons - Audio amplifier - Pi Pico Power options include USB (in which case, omit the battery case and power switch) or batteries (remove D1 from the Pi Pico in this case). .. image:: images/swiss-army-pcb.jpg :alt: Swiss-army PCB Details of the Swiss-Army-PCB can be found `here `_. Firmware -------- Here is the C++ code along with a pre-built firmware image available for download over USB. - `C++ Code for Pi Pico `_ - `Pico Firmware `_ Software -------- The timer code itself is intentionally kept simple but with a fun twist. Instead of generic beeps, the code includes a selection of melodies to signal the end of the timer. The C++ code snippet for the timer logic is as follows: .. code:: cpp if (running) { if (time_us_64() >= next_run) { next_run += 1000000u; if (seconds == 0) { if (minutes == 0) { if (hours == 0) { // timer expired running = false; hours = stored_hours; minutes = stored_minutes; seconds = 0; needs_reset = false; uint64_t start_time = time_us_64(); // update timer 1 second from now while((time_us_64() - start_time) < timeout) { player.play_tune(audio_output, tunes[tune], num_notes[tune]); ++tune; } } else { minutes = 59; --hours; } } else { --minutes; seconds = 59; } } else { --seconds; } } } Simple Tune Player Class ------------------------ Here is a simple class designed to play a musical tune. Each musical note is represented by a structure (`note_t`) containing the frequency in Hertz and the duration in samples. A rest is indicated by setting the frequency to zero. The `play_tune` function is responsible for playing an array of notes, utilizing the `play_note` function for individual note playback. The `play_note` function features a phase accumulator, which increments by a fixed number of steps for each sample. The ten most significant bits of the phase accumulator serve as an index into a sine (`sin`) lookup table. The appropriate number of samples is then passed to the PWM audio output. This version introduces a small gap between each note; however, a more sophisticated solution would involve applying an envelope to the amplitude of the audio signal to better represent a musical note. .. code:: cpp void music::play_note(PWMAudio audio_output, note_t note) { const uint32_t frequency_steps = static_cast(4294967296.0 * note.pitch_Hz / audio_sample_rate); const float amplitude = note.pitch_Hz == 0 ? 0 : 2047; // frequency of zero means silence uint32_t samples_left = note.duration_samples - 50; while (samples_left) { uint32_t block_size = std::min(static_cast(1024u), samples_left); for (uint16_t idx = 0; idx < block_size; idx++) { const float sample = sin_table[phase >> 22] * amplitude; // 10 MSBs (32-10 = 22) phase += frequency_steps; samples[ping][idx] = sample + 2048; } audio_output.output_samples(samples[ping], block_size); ping ^= 1u; samples_left -= block_size; } // small gap between notes for (uint16_t idx = 0; idx < 50; idx++) { samples[ping][idx] = 2048; } audio_output.output_samples(samples[ping], 50); ping ^= 1u; } void music::play_tune(PWMAudio audio_output, const note_t notes[], uint16_t length) { // play each note in turn for(uint16_t i=0; i