Skip to content

fix(examples): Migrate buzzer tone() to hardware PWM Timer.#273

Merged
nedseb merged 2 commits intomainfrom
refactor/migrate-buzzer-to-timer
Mar 28, 2026
Merged

fix(examples): Migrate buzzer tone() to hardware PWM Timer.#273
nedseb merged 2 commits intomainfrom
refactor/migrate-buzzer-to-timer

Conversation

@Charly-sketch
Copy link
Copy Markdown
Contributor

Summary

Replace the blocking software-based tone() implementation in dpad_piano.py with a hardware PWM-based approach using Timer 1.

Closes #254


Changes

  • Replace GPIO bit-banging (sleep_us loop) with hardware PWM (TIM1_CH4 on PA11 / SPEAKER)
  • Add non-blocking tone() and no_tone() functions using Timer PWM
  • Improve responsiveness of D-PAD and MENU buttons while playing notes
  • Align implementation with metal_detector.py example (already using PWM)

Checklist

  • ruff check passes
  • Tested on hardware (if applicable)
  • Examples added/updated (if applicable)
  • Commit messages follow <scope>: <Description.> format

@Charly-sketch Charly-sketch requested a review from nedseb March 27, 2026 09:16
@nedseb nedseb requested a review from Copilot March 27, 2026 10:53
@nedseb nedseb added the bug Something isn't working label Mar 27, 2026
Copy link
Copy Markdown
Contributor

@nedseb nedseb left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review — PR #273

Très bon travail, rien à corriger.

Points positifs

  • Migration propre vers Timer PWM, alignée avec metal_detector.py (#251)
  • tone() est non-bloquant : le son joue pendant que la boucle continue de poller les boutons et le MENU
  • finally: no_tone() garantit le silence à la sortie (Ctrl+C ou MENU)
  • Tracking intelligent avec last_freq pour éviter les appels redondants à no_tone()
  • Son continu tant qu'on maintient un bouton — comportement naturel pour un piano

Prêt à merger.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Migrates the dpad_piano.py example from a blocking, software bit-banged buzzer implementation to a hardware PWM-based approach using Timer 1, improving button responsiveness during note playback.

Changes:

  • Replaces GPIO toggling (sleep_us loop) with TIM1_CH4 PWM on the SPEAKER pin.
  • Introduces non-blocking tone() and no_tone() helpers backed by pyb.Timer.
  • Updates the main loop to start/stop PWM based on current D-PAD input and ensure the buzzer is silenced on exit.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 124 to 144
last_note = None
last_octave = None
last_freq = None

try:
while True:
# MENU button is a direct MCU pin, not an MCP23009E pin
if MENU_BUTTON.value() == 0:
print("Menu button pressed, exiting piano.")
wait_menu_release()
break

pressed = get_pressed_buttons()
frequency, note_name, octave = select_frequency(pressed)

if pressed:
frequency, note_name, octave = select_frequency(pressed)

if note_name != last_note or octave != last_octave:
print(
"Playing:",
note_name,
"-",
frequency,
"Hz",
"(" + octave + " octave)",
)
last_note = note_name
last_octave = octave

tone(SPEAKER, frequency, 60)
else:
if frequency == 0:
if last_freq != 0:
no_tone()
last_note = None
last_octave = None
last_freq = 0
sleep_ms(20)
Copy link

Copilot AI Mar 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

last_freq is initialized to None but later compared to 0, which causes an extra no_tone() call on the first loop iteration even before any note is played. Consider initializing last_freq to 0 (or removing last_freq entirely and calling no_tone() unconditionally when frequency == 0, since it is idempotent).

Copilot uses AI. Check for mistakes.
Comment thread lib/mcp23009e/examples/dpad_piano.py Outdated
def tone(freq):
"""Start a tone using hardware PWM."""
if freq <= 0:
buzzer_ch.pulse_width_percent(0)
Copy link

Copilot AI Mar 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In tone(), the freq <= 0 path directly sets buzzer_ch.pulse_width_percent(0) instead of reusing no_tone(). Calling no_tone() here would keep the silencing logic in one place (and makes future changes less error-prone).

Suggested change
buzzer_ch.pulse_width_percent(0)
no_tone()

Copilot uses AI. Check for mistakes.
@nedseb
Copy link
Copy Markdown
Contributor

nedseb commented Mar 28, 2026

Les deux remarques de Copilot ont été corrigées dans ef28b74 :

  1. last_freq initialisé à 0 au lieu de None pour éviter un appel parasite à no_tone() au premier tour de boucle.
  2. Appel à no_tone() dans tone() au lieu de dupliquer buzzer_ch.pulse_width_percent(0).

C'étaient des corrections rapides et évidentes, elles n'auraient pas dû rester en attente.

@nedseb nedseb merged commit 54a46e2 into main Mar 28, 2026
7 checks passed
@nedseb nedseb deleted the refactor/migrate-buzzer-to-timer branch March 28, 2026 05:56
semantic-release-updater Bot pushed a commit that referenced this pull request Mar 28, 2026
## [0.1.3](v0.1.2...v0.1.3) (2026-03-28)

### Bug Fixes

* **examples:** Migrate buzzer tone() to hardware PWM Timer. ([#273](#273)) ([54a46e2](54a46e2))
@semantic-release-updater
Copy link
Copy Markdown

🎉 This PR is included in version 0.1.3 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working released

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix(examples): Migrate buzzer tone() to hardware PWM Timer.

3 participants