用OLED+旋轉編碼旋鈕做單迴路控制器
遇到一個問題,時間會被電腦更改,所以要防止內部時間被Thonny連線修改,因為使用場所時間會跟電腦不同,也是要這個問題弄很久。
ESP32 OLED 旋转编码器定时器项目! 🎉
你今天完成了:
✅ 旋转编码器精准控制菜单和时间编辑
✅ ESP32 内部时间正确更新,不受 Thonny 影响
✅ 修改 Now 后,ESP32 时间同步,OLED 上时间继续走时
✅ 继电器在设定时间 On / Off 时自动切换
✅ 编辑模式下,当前修改的数值闪烁,用户体验更直观
ESP32 C3接腳圖
GPIO20-RX
GPIO21-TX
# 2025/02/03-21:30
from machine import Pin, I2C
import ssd1306
import time
import utime # ESP32 内部时间模块
# 初始化 I2C(OLED)
i2c = I2C(0, scl=Pin(9), sda=Pin(8), freq=400000)
oled = ssd1306.SSD1306_I2C(128, 64, i2c)
# 旋转编码器
clk = Pin(3, Pin.IN, Pin.PULL_UP)
dt = Pin(4, Pin.IN, Pin.PULL_UP)
sw = Pin(5, Pin.IN, Pin.PULL_UP)
# 继电器 GPIO
relay = Pin(10, Pin.OUT)
relay_state = False # 继电器默认关闭
# 变量
current_row = 1 # 选中的行(1~4)
edit_mode = False # 是否进入编辑模式
edit_part = 0 # 0=未编辑, 1=编辑小时, 2=编辑分钟
last_clk = clk.value()
blink_state = True # : 闪烁状态
editing_now = False # 控制 Now 是否自动更新时间
time_after_edit = False # 防止 Now 立即被 update_now() 覆盖
last_edit_time = 0 # 记录退出编辑 Now 的时间
# 时间参数
now_hour, now_min = 12, 0 # 初始化
on_hour, on_min = 8, 0
off_hour, off_min = 18, 0
def update_now():
"""更新 ESP32 当前时间到 Now,但防止修改后立即被覆盖"""
global now_hour, now_min
if editing_now or time_after_edit: # ✅ 仅当 time_after_edit 过期后才更新 Now
return
t = utime.localtime()
now_hour, now_min = t[3], t[4] # 只在正常情况下更新时间
def update_esp_time():
"""同步 `Now` 到 ESP32 内部时间,并防止 Thonny 自动同步 PC 时间"""
global now_hour, now_min, time_after_edit, last_edit_time
# ✅ **获取 ESP32 当前日期**
year, month, day, weekday, _, _, _, _ = utime.localtime()
# ✅ **确保 `now_hour` 和 `now_min` 已定义**
try:
if now_hour is None or now_min is None:
raise NameError # 如果 `now_hour` 和 `now_min` 未定义,触发异常
except NameError:
print("⚠️ `now_hour` 或 `now_min` 未初始化,使用系统时间")
now_hour, now_min = utime.localtime()[3], utime.localtime()[4]
# ✅ **手动设置 ESP32 RTC,防止 Thonny 自动同步 PC 时间**
import machine
rtc = machine.RTC()
rtc.datetime((year, month, day, weekday, now_hour, now_min, 0, 0))
print(f"🕒 ESP32 时间已最终同步: {now_hour:02}:{now_min:02}")
# ✅ 直接等待 2 秒后恢复 `Now` 自动更新时间
time_after_edit = True
last_edit_time = utime.time()
# OLED 更新
def update_display():
oled.fill(0) # 清屏
separator = ":" if blink_state else " " # `:` 闪烁
# **编辑模式 - 让正在编辑的数值闪烁**
def draw_text(text, x, y, hide=False):
"""绘制 OLED 文字,支持闪烁隐藏"""
if not hide:
oled.text(text, x, y)
# **Now 显示**
draw_text("Now:", 10, 0)
draw_text(f"{now_hour:02}", 45, 0, hide=(edit_mode and current_row == 1 and edit_part == 1 and blink_state))
draw_text(separator, 65, 0)
draw_text(f"{now_min:02}", 75, 0, hide=(edit_mode and current_row == 1 and edit_part == 2 and blink_state))
# **On 显示**
draw_text("On :", 10, 16)
draw_text(f"{on_hour:02}", 45, 16, hide=(edit_mode and current_row == 2 and edit_part == 1 and blink_state))
draw_text(":", 65, 16)
draw_text(f"{on_min:02}", 75, 16, hide=(edit_mode and current_row == 2 and edit_part == 2 and blink_state))
# **Off 显示**
draw_text("Off:", 10, 32)
draw_text(f"{off_hour:02}", 45, 32, hide=(edit_mode and current_row == 3 and edit_part == 1 and blink_state))
draw_text(":", 65, 32)
draw_text(f"{off_min:02}", 75, 32, hide=(edit_mode and current_row == 3 and edit_part == 2 and blink_state))
# **SW 显示**
draw_text(f"SW : {'ON' if relay_state else 'OFF'}", 10, 48)
# **普通模式 `>`,编辑模式 `*`**
indicator = "*" if edit_mode else ">"
oled.text(indicator, 0, (current_row - 1) * 16) # 选中行
oled.show()
# 初始化 OLED
update_now() # 获取 ESP32 当前时间
update_display()
# 记录上次更新时间(避免每秒刷新 OLED)
last_time_update = utime.time()
while True:
current_clk = clk.value()
current_dt = dt.value()
#修改为 0.5 秒
if utime.time() - last_time_update >= 0.5:
blink_state = not blink_state # **切换 `:` 和 ` `,让编辑数值闪烁**
update_display() # **刷新 OLED**
last_time_update = utime.time()
if not editing_now:
update_now()
last_time_update = utime.time()
update_display()
if (now_hour, now_min) == (on_hour, on_min):
relay_state = True
relay.value(1)
print("⏰ 继电器自动开启!")
elif (now_hour, now_min) == (off_hour, off_min):
relay_state = False
relay.value(0)
print("⏰ 继电器自动关闭!")
update_display()
# **防止 `update_now()` 立即覆盖 `Now`,等待 2 秒后恢复**
if time_after_edit and utime.time() - last_edit_time > 2:
time_after_edit = False # ✅ 2 秒后恢复 `Now` 自动更新时间
update_now() # ✅ **恢复 `Now` 走时**
print("✅ `Now` 自动更新时间恢复")
# **旋转编码器检测**
if last_clk == 1 and current_clk == 0:
if current_dt == 1:
delta = 1
else:
delta = -1
if not edit_mode:
current_row = max(1, min(4, current_row + delta))
update_display()
else:
if current_row == 1:
if edit_part == 1:
now_hour = (now_hour + delta) % 24
else:
now_min = (now_min + delta) % 60
elif current_row == 2:
if edit_part == 1:
on_hour = (on_hour + delta) % 24
else:
on_min = (on_min + delta) % 60
elif current_row == 3:
if edit_part == 1:
off_hour = (off_hour + delta) % 24
else:
off_min = (off_min + delta) % 60
update_display()
last_clk = current_clk
# **按键检测**
if sw.value() == 0:
time.sleep(0.2)
if sw.value() == 0:
if not edit_mode:
if current_row == 4:
relay_state = not relay_state
relay.value(1 if relay_state else 0)
elif current_row == 1:
editing_now = True
edit_mode = True
edit_part = 1
elif current_row in [2, 3]:
edit_mode = True
edit_part = 1
else:
if edit_part == 1:
edit_part = 2
else:
edit_mode = False
edit_part = 0
if current_row == 1:
editing_now = False
update_esp_time()
last_edit_time = utime.time()
update_display()
time.sleep(0.2)
time.sleep(0.005)