简介
本章节将尝试完成电子测光表的进阶任务。 实际上一共三个进阶任务。 即
1- 使用手机控制舵机拍照
2- 菜单切换BH1750测光模式
3- 蓝牙控制舵机
但是实际上如果要使用Circuit Python的话, 这个ESP32S3 - TFT feature 开发板并不支持蓝牙。

所以如果不使用Arduino , 或者其他固件的话即无法使用蓝牙功能, 或者像群里某个大佬一样实现使用WEB网页的方式控制, 不过这种较为麻烦。所以本文将带来,BH1750 切换测光模式的实现和 使用按钮控制舵机的实现。
BH1750 测光菜单切换测光模式和分辨率

BH1750 的测光,在库定义中一共有三个模式, 第一个是停止模式, 第二个是连续测光,第三个是单次测光。
对于分辨率的设置一共也是有三种,反别是Low (低), MID (中) 和, HIGH (高)

我们可以修改代码, 根据按键的状态来切换分辨率和测光的模式。
直接在之前的code.py 中修改, 主要是增加了按键的控制切换。
import time
import math
import board
import adafruit_bh1750
import digitalio
import terminalio
from adafruit_display_text import bitmap_label
import displayio
# 初始化 I2C 和 BH1750 传感器
i2c = board.I2C() # 使用板载 SCL 和 SDA
sensor = adafruit_bh1750.BH1750(i2c)
# 计算 EV (曝光值) 的函数
def calculate_ev(lux):
return 2 + math.log(lux / 10, 2) if lux > 0 else 0
# 计算快门速度的函数,基于 EV、光圈值和 ISO
def calculate_shutter_speed(ev, aperture, iso):
# Shutter speed = (aperture ^ 2) / (ISO * (2 ^ EV))
return (aperture ** 2) / (iso * (2 ** ev))
# 初始化按钮 D13 和 D12
button_up = digitalio.DigitalInOut(board.D13)
button_up.direction = digitalio.Direction.INPUT
button_up.pull = digitalio.Pull.UP # 启用上拉电阻
button_down = digitalio.DigitalInOut(board.D12)
button_down.direction = digitalio.Direction.INPUT
button_down.pull = digitalio.Pull.UP # 启用上拉电阻
# 初始化显示
group = displayio.Group()
board.DISPLAY.root_group = group
# 创建不同文本区域的标签,设置 scale=1 统一字体大小
shutter_label = bitmap_label.Label(terminalio.FONT, text="", scale=1)
shutter_label.x = 10
shutter_label.y = 10
group.append(shutter_label)
aperture_label = bitmap_label.Label(terminalio.FONT, text="", scale=1)
aperture_label.x = 10
aperture_label.y = 30
group.append(aperture_label)
iso_label = bitmap_label.Label(terminalio.FONT, text="", scale=1)
iso_label.x = 10
iso_label.y = 50
group.append(iso_label)
mode_label = bitmap_label.Label(terminalio.FONT, text="", scale=1)
mode_label.x = 10
mode_label.y = 70
group.append(mode_label)
resolution_label = bitmap_label.Label(terminalio.FONT, text="", scale=1)
resolution_label.x = 10
resolution_label.y = 90
group.append(resolution_label)
# 初始设置
aperture = 3.5 # 固定光圈值
iso = 100 # 初始 ISO 值
# 定义 BH1750 测光模式
modes = [
"Continuous", # 连续高分辨率模式
"One Time", # 单次高分辨率模式
"STOP" # 停止模式
]
current_mode_index = 0 # 默认使用 "Continuous" 模式
# 定义 BH1750 分辨率模式
resolutions = [
"LOW", # 低分辨率模式
"MID", # 中分辨率模式
"HIGH" # 高分辨率模式
]
current_resolution_index = 0 # 默认使用 "LOW" 分辨率模式
# 切换测光模式的函数
def switch_mode():
global current_mode_index
current_mode_index = (current_mode_index + 1) % len(modes)
# 根据选择的模式设置传感器
if current_mode_index == 0:
sensor.mode = adafruit_bh1750.Mode.CONTINUOUS
elif current_mode_index == 1:
sensor.mode = adafruit_bh1750.Mode.ONE_SHOT
# 切换分辨率模式的函数
def switch_resolution():
global current_resolution_index
current_resolution_index = (current_resolution_index + 1) % len(resolutions)
# 根据选择的分辨率设置传感器
if current_resolution_index == 0:
sensor.resolution = adafruit_bh1750.Resolution.LOW
elif current_resolution_index == 1:
sensor.resolution = adafruit_bh1750.Resolution.MID
elif current_resolution_index == 2:
sensor.resolution = adafruit_bh1750.Resolution.HIGH
# 主循环
while True:
lux = sensor.lux # 从传感器读取 Lux 值
ev = calculate_ev(lux) # 从 Lux 计算 EV 值
# 切换测光模式(使用按钮 D12)
if not button_down.value: # D12 按钮被按下
switch_mode()
time.sleep(0.2) # 防抖延迟
# 切换分辨率模式(使用按钮 D13)
if not button_up.value: # D13 按钮被按下
switch_resolution()
time.sleep(0.2) # 防抖延迟
# 基于固定光圈计算快门速度
shutter_speed = calculate_shutter_speed(ev, aperture, iso)
# 更新显示
shutter_label.text = f"Shutter: 1/{int(1 / shutter_speed)}s" if shutter_speed > 0 else "Shutter: --"
aperture_label.text = f"Aperture: f/{aperture:.1f}" # 固定光圈值
iso_label.text = f"ISO: {iso}"
mode_label.text = f"Mode: {modes[current_mode_index]}"
resolution_label.text = f"Resolution: {resolutions[current_resolution_index]}"
time.sleep(0.1)这样当用户按下按键的时候就可以切换BH1750的测光模式和分辨率。

360度舵机控制
Adafruit家提供的库非常丰富, 我们可以下载adafruit-circuitpython-bundle-9.x-mpy-20241121 地址: https://github.com/adafruit/Adafruit_CircuitPython_Bundle/releases 这个压缩包里包括了几乎所有的Cpy的库和demo,我们需要用到的是舵机的相关库。

把这个文件夹, 拷贝到开发板的lib目录下。
然后可以使用下述代码来控制舵机的正转和反转。
import time
import board
import digitalio
import pwmio
# 设置 D12 和 D13 按钮
button_rotate = digitalio.DigitalInOut(board.D12)
button_rotate.direction = digitalio.Direction.INPUT
button_rotate.pull = digitalio.Pull.UP # 上拉电阻
button_stop = digitalio.DigitalInOut(board.D13)
button_stop.direction = digitalio.Direction.INPUT
button_stop.pull = digitalio.Pull.UP # 上拉电阻
# 设置 D11 用于 PWM 信号控制舵机
pwm = pwmio.PWMOut(board.D11, duty_cycle=2**15, frequency=50)
# 变量来跟踪舵机当前的旋转方向,True 表示正转,False 表示反转
is_forward = True
# 控制舵机旋转的函数
def move_servo(duty_cycle):
pwm.duty_cycle = duty_cycle # 调整 PWM 占空比来控制旋转
# 主循环
while True:
# 检查 D12 按钮是否被按下,切换方向
if not button_rotate.value: # 按下时切换方向
if is_forward:
print("Switching to reverse")
move_servo(8000)
else:
print("Switching to forward")
move_servo(4000)
is_forward = not is_forward # 切换方向
time.sleep(0.2) # 防抖延迟
# 如果 D13 按钮被按下,舵机停止
elif not button_stop.value:
print("Stopping")
move_servo(5000) # 停止,占空比接近 1.5ms(相对于 50Hz 的频率)
time.sleep(0.2) # 防抖延迟由于这个舵机是一个360度的舵机,所有没办法控制具体的角度, 只能通过PWM的占空比等来调整方向和速度。上文中代码设置的反转的速度是正转的两倍, 我们可以通过控制修改这个占空比来调整速度。

屏幕显示部分

- 更新日期 2024 / 12 /2 日
- 更新日志 : 增加了WIFI链接和MQTT集成Home Assistant, 通过Home Assistant实现控制舵机和数据的回显。
代码如下
import time
import math
import board
import adafruit_bh1750
import digitalio
import terminalio
from adafruit_display_text import bitmap_label
import displayio
import os
import ssl
import socketpool
import wifi
import json
import pwmio
import adafruit_minimqtt.adafruit_minimqtt as MQTT
aio_username = "你的MQTT账号"
aio_key = "你的MQTT密码"
print(aio_username)
wifi.radio.connect("你的wifi账号","你的wifi密码")
print(wifi.radio.ipv4_address)
topic = "user/motor"
sensor_topic = "user/sensor"
# Define callback methods which are called when events occur
def connected(client, userdata, flags, rc):
# This function will be called when the client is connected
# successfully to the broker.
print(f"Connected to MQTT Server! Listening for topic changes on {topic}")
# Subscribe to all changes on the onoff_feed.
client.subscribe(topic)
print("Connected to motor topic")
client.subscribe(sensor_topic)
print("Connected to sensor topic")
def disconnected(client, userdata, rc):
# This method is called when the client is disconnected
print("Disconnected from Adafruit IO!")
is_forward = True
pwm = pwmio.PWMOut(board.D11, duty_cycle=2**15, frequency=50)
# 控制舵机旋转的函数
def move_servo(duty_cycle):
pwm.duty_cycle = duty_cycle # 调整 PWM 占空比来控制旋转
def message(client, topic, message):
print(f"New message on topic {topic}: {message}")
global is_forward
if topic == "user/motor":
if message == "Switch":
if is_forward:
move_servo(8000) # 如果是前进方向,设置 PWM 占空比
else:
move_servo(4000) # 如果是后退方向,设置 PWM 占空比
is_forward = not is_forward # 切换方向
elif message == "OFF":
print("Turning servo OFF")
move_servo(5000) # 关闭舵机(可根据需求调整)
time.sleep(0.2)
pool = socketpool.SocketPool(wifi.radio)
ssl_context = ssl.create_default_context()
# Set up a MiniMQTT Client
mqtt_client = MQTT.MQTT(
broker="192.168.1.113",
port=1883,
socket_pool=pool,
ssl_context=None,
)
# Setup the callback methods above
mqtt_client.on_connect = connected
mqtt_client.on_disconnect = disconnected
mqtt_client.on_message = message
# Connect the client to the MQTT broker.
print("Connecting to MQTT Server...")
mqtt_client.connect()
print("Connected...")
photocell_val = 0
# 变量来跟踪舵机当前的旋转方向,True 表示正转,False 表示反转
is_forward = True
# 初始化 I2C 和 BH1750 传感器
i2c = board.I2C() # 使用板载 SCL 和 SDA
sensor = adafruit_bh1750.BH1750(i2c)
# 计算 EV (曝光值) 的函数
def calculate_ev(lux):
return 2 + math.log(lux / 10, 2) if lux > 0 else 0
# 计算快门速度的函数,基于 EV、光圈值和 ISO
def calculate_shutter_speed(ev, aperture, iso):
# Shutter speed = (aperture ^ 2) / (ISO * (2 ^ EV))
return (aperture ** 2) / ((2 ** ev))
# 初始化按钮 D13 和 D12
button_up = digitalio.DigitalInOut(board.D13)
button_up.direction = digitalio.Direction.INPUT
button_up.pull = digitalio.Pull.UP # 启用上拉电阻
button_down = digitalio.DigitalInOut(board.D12)
button_down.direction = digitalio.Direction.INPUT
button_down.pull = digitalio.Pull.UP # 启用上拉电阻
# 初始化显示
group = displayio.Group()
board.DISPLAY.root_group = group
# 创建不同文本区域的标签,设置 scale=1 统一字体大小
shutter_label = bitmap_label.Label(terminalio.FONT, text="", scale=1)
shutter_label.x = 10
shutter_label.y = 10
group.append(shutter_label)
aperture_label = bitmap_label.Label(terminalio.FONT, text="", scale=1)
aperture_label.x = 10
aperture_label.y = 30
group.append(aperture_label)
iso_label = bitmap_label.Label(terminalio.FONT, text="", scale=1)
iso_label.x = 10
iso_label.y = 50
group.append(iso_label)
mode_label = bitmap_label.Label(terminalio.FONT, text="", scale=1)
mode_label.x = 10
mode_label.y = 70
group.append(mode_label)
resolution_label = bitmap_label.Label(terminalio.FONT, text="", scale=1)
resolution_label.x = 10
resolution_label.y = 90
group.append(resolution_label)
lux_label = bitmap_label.Label(terminalio.FONT, text="", scale=1)
lux_label.x = 10
lux_label.y = 110 # 显示在最后一行
group.append(lux_label)
# 初始设置
aperture = 3.5 # 固定光圈值
iso = 100 # 初始 ISO 值
# 定义 BH1750 测光模式
modes = [
"Continuous", # 连续高分辨率模式
"One Time", # 单次高分辨率模式
"STOP" # 停止模式
]
current_mode_index = 0 # 默认使用 "Continuous" 模式
# 定义 BH1750 分辨率模式
resolutions = [
"LOW", # 低分辨率模式
"MID", # 中分辨率模式
"HIGH" # 高分辨率模式
]
current_resolution_index = 0 # 默认使用 "LOW" 分辨率模式
# 切换测光模式的函数
def switch_mode():
global current_mode_index
current_mode_index = (current_mode_index + 1) % len(modes)
# 根据选择的模式设置传感器
if current_mode_index == 0:
sensor.mode = adafruit_bh1750.Mode.CONTINUOUS
elif current_mode_index == 1:
sensor.mode = adafruit_bh1750.Mode.ONE_SHOT
# 切换分辨率模式的函数
def switch_resolution():
global current_resolution_index
current_resolution_index = (current_resolution_index + 1) % len(resolutions)
# 根据选择的分辨率设置传感器
if current_resolution_index == 0:
sensor.resolution = adafruit_bh1750.Resolution.LOW
elif current_resolution_index == 1:
sensor.resolution = adafruit_bh1750.Resolution.MID
elif current_resolution_index == 2:
sensor.resolution = adafruit_bh1750.Resolution.HIGH
# 主循环
while True:
mqtt_client.loop(timeout=1)
lux = sensor.lux # 从传感器读取 Lux 值
ev = calculate_ev(lux) # 从 Lux 计算 EV 值
# 切换测光模式(使用按钮 D12)
if not button_down.value: # D12 按钮被按下
switch_mode()
time.sleep(0.2) # 防抖延迟
# 切换分辨率模式(使用按钮 D13)
if not button_up.value: # D13 按钮被按下
switch_resolution()
time.sleep(0.2) # 防抖延迟
# 基于固定光圈计算快门速度
shutter_speed = calculate_shutter_speed(ev, aperture, iso)
# 更新显示
shutter_label.text = f"Shutter: 1/{int(1 / shutter_speed)}s" if shutter_speed > 0 else "Shutter: --"
aperture_label.text = f"Aperture: f/{aperture:.1f}" # 固定光圈值
iso_label.text = f"ISO: {iso}"
mode_label.text = f"Mode: {modes[current_mode_index]}"
resolution_label.text = f"Resolution: {resolutions[current_resolution_index]}"
lux_label.text = f"Light: {lux:.2f} Lux" # 更新光照值显示
data = {
"lux": lux,
"ev": ev,
"shutter_speed": f"1/{int(1 / shutter_speed)}" if shutter_speed > 0 else "--",
"aperture": aperture,
"iso": iso,
"mode": modes[current_mode_index],
"resolution": resolutions[current_resolution_index],
}
mqtt_client.publish(sensor_topic, json.dumps(data)) # 发送 JSON 数据
time.sleep(0.1)效果展示

手机端效果展示

视频展示
https://www.bilibili.com/video/BV1TD6MYVET1/?vd_source=fa81ee8dac5a78e9ccb692c6642f6fe2
我要赚赏金
