from machine import Pin, I2C, PWM
from lcd_api import LcdApi
from i2c_lcd import I2cLcd
import utime
# ===================== 硬件配置 =====================
# 1. 引脚定义
BUZZER_PIN = 32 # 蜂鸣器PWM引脚
LED_PIN = Pin(25, Pin.OUT, value=0) # LED引脚(初始熄灭)
# 按键引脚(内部上拉,按下接GND)
BTN_HOUR_PLUS = Pin(18, Pin.IN, Pin.PULL_UP) # 黑色按钮(小时+)
BTN_MIN_PLUS = Pin(19, Pin.IN, Pin.PULL_UP) # 蓝色按钮(分钟+)
BTN_SEC_PLUS = Pin(17, Pin.IN, Pin.PULL_UP) # 红色按钮(秒+5)
BTN_START_PAUSE = Pin(26, Pin.IN, Pin.PULL_UP)# 灰色按钮(开始/暂停)
BTN_CLEAR_STOP = Pin(27, Pin.IN, Pin.PULL_UP) # 绿色按钮(清空/停止)
# 2. LCD1602 I2C初始化
I2C_ADDR = 0x27 # LCD默认地址,无显示改0x3F
TOTAL_ROWS = 2 # LCD行数
TOTAL_COLS = 16 # LCD列数
i2c = I2C(1, scl=Pin(22), sda=Pin(21), freq=400000) # ESP32 I2C1引脚
lcd = I2cLcd(i2c, I2C_ADDR, TOTAL_ROWS, TOTAL_COLS)
# ===================== 音乐配置 =====================
# 简谱1-7对应C调频率(0=休止符)
NOTE_FREQ = {
0: 0, # 休止符(仅标记,不设freq=0)
1: 523, # Do
2: 587, # Re
3: 659, # Mi
4: 698, # Fa
5: 784, # Sol
6: 880, # La
7: 988 # Si
}
# 用户指定的旋律
MUSIC = [1,1,5,5,6,6,5,0,
5,5,4,4,3,3,2,0,
5,5,4,4,3,3,2,0,
1,1,5,5,6,6,5,0,
4,4,3,3,2,2,1,0]
NOTE_DURATION = 300 # 每个音符时长(毫秒)
# ===================== 全局变量 =====================
target_hour = 0 # 设置的目标小时
target_min = 0 # 设置的目标分钟
target_sec = 0 # 设置的目标秒
remain_hour = 0 # 倒计时剩余小时
remain_min = 0 # 倒计时剩余分钟
remain_sec = 0 # 倒计时剩余秒
is_running = False # 倒计时运行状态
is_playing = False # 音乐播放状态
last_millis = 0 # 倒计时计时基准
# 蜂鸣器PWM初始化(freq=1合法,duty=0静音)
buzzer_pin = Pin(BUZZER_PIN)
buzzer = PWM(buzzer_pin, freq=1, duty=0)
# ===================== 辅助函数 =====================
# 更新LCD显示内容
def update_display():
lcd.clear()
lcd.set_cursor(0, 0)
# 运行中显示剩余时间,否则显示目标时间
show_h = remain_hour if is_running else target_hour
show_m = remain_min if is_running else target_min
show_s = remain_sec if is_running else target_sec
lcd.print(f"Time: {show_h}:{show_m}:{show_s}")
# 按键检测(消抖,返回True表示按键有效按下)
def check_button(btn):
if btn.value() == 0:
utime.sleep_ms(100) # 消抖延时
if btn.value() == 0:
while btn.value() == 0: # 等待按键松开
utime.sleep_ms(20)
return True
return False
# 播放音乐(同步点亮LED)
def play_music():
global is_playing
is_playing = True
LED_PIN.value(1) # 播放音乐时LED常亮
for note in MUSIC:
# 绿色按钮可中途停止音乐
if check_button(BTN_CLEAR_STOP) or not is_playing:
break
target_freq = NOTE_FREQ[note]
if target_freq == 0: # 休止符:仅关闭占空比(静音)
buzzer.duty(0)
else: # 播放音符:设置频率+打开占空比
buzzer.freq(target_freq)
buzzer.duty(512) # 占空比50%(音量适中)
utime.sleep_ms(NOTE_DURATION)
# 播放结束/停止后,关闭蜂鸣器和LED
buzzer.duty(0)
LED_PIN.value(0)
is_playing = False
# 初始化外设
def setup():
LED_PIN.value(0) # 初始熄灭LED
buzzer.duty(0) # 初始静音蜂鸣器
lcd.clear() # 清空LCD
lcd.backlight = True # 开启LCD背光
lcd.print("Time: 0:0:0") # 初始显示
print("系统启动,等待按键...")
# ===================== 主逻辑 =====================
def main():
setup()
while True:
# 1. 绿色按钮(最高优先级:强制停止所有)
if check_button(BTN_CLEAR_STOP):
# 声明要修改的全局变量
global target_hour, target_min, target_sec, remain_hour, remain_min, remain_sec
global is_running, is_playing
# 重置所有时间和状态
target_hour = target_min = target_sec = 0
remain_hour = remain_min = remain_sec = 0
is_running = False
is_playing = False
# 强制关闭蜂鸣器和LED
buzzer.duty(0)
LED_PIN.value(0)
# 更新显示并提示
update_display()
print("强制停止!所有功能已重置")
continue # 跳过后续逻辑,确保停止生效
# 2. 音乐播放中仅响应绿色按钮
if is_playing:
continue
# 3. 时间设置按键(黑色/蓝色/红色)
# 黑色按钮:小时+1
if check_button(BTN_HOUR_PLUS):
global target_hour
target_hour += 1
print(f"小时+1 → 当前:{target_hour}")
update_display()
# 蓝色按钮:分钟+1(自动进位)
if check_button(BTN_MIN_PLUS):
global target_min
target_min += 1
if target_min >= 60:
target_hour += 1
target_min = 0
print(f"分钟+1 → 当前:{target_min}")
update_display()
# 红色按钮:秒+5(自动进位)
if check_button(BTN_SEC_PLUS):
global target_sec
target_sec += 5
if target_sec >= 60:
target_min += target_sec // 60
target_sec = target_sec % 60
if target_min >= 60:
target_hour += target_min // 60
target_min = target_min % 60
print(f"秒+5 → 当前:{target_sec}")
update_display()
# 4. 灰色按钮:开始/暂停倒计时
if check_button(BTN_START_PAUSE):
global is_running, remain_hour, remain_min, remain_sec, last_millis
is_running = not is_running
if is_running:
# 同步剩余时间为目标时间
remain_hour = target_hour
remain_min = target_min
remain_sec = target_sec
last_millis = utime.ticks_ms()
print(f"倒计时启动 → {remain_hour}:{remain_min}:{remain_sec}")
else:
print("倒计时暂停")
update_display()
# 5. 倒计时逻辑(非阻塞计时)
if is_running:
current_millis = utime.ticks_ms()
# 每1秒更新一次倒计时
if utime.ticks_diff(current_millis, last_millis) >= 1000:
last_millis = current_millis
global remain_sec, remain_min, remain_hour
remain_sec -= 1
# 秒借位
if remain_sec < 0:
remain_sec = 59
remain_min -= 1
# 分钟借位
if remain_min < 0:
remain_min = 59
remain_hour -= 1
# 小时借位(倒计时结束)
if remain_hour < 0:
remain_hour = remain_min = remain_sec = 0
is_running = False
print("倒计时结束!开始播放音乐...")
play_music() # 触发音乐播放
update_display()
# ===================== 启动程序 =====================
if __name__ == "__main__":
main()